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 collections::HashMap;
   17use futures::StreamExt;
   18use gpui::{
   19    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   20    VisualTestContext, WindowBounds, WindowOptions, div,
   21};
   22use indoc::indoc;
   23use language::{
   24    BracketPairConfig,
   25    Capability::ReadWrite,
   26    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   27    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   28    language_settings::{
   29        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   30        SelectedFormatter,
   31    },
   32    tree_sitter_python,
   33};
   34use language_settings::Formatter;
   35use lsp::CompletionParams;
   36use multi_buffer::{IndentGuide, PathKey};
   37use parking_lot::Mutex;
   38use pretty_assertions::{assert_eq, assert_ne};
   39use project::{
   40    FakeFs,
   41    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   42    project_settings::LspSettings,
   43};
   44use serde_json::{self, json};
   45use settings::{
   46    AllLanguageSettingsContent, IndentGuideBackgroundColoring, IndentGuideColoring,
   47    ProjectSettingsContent,
   48};
   49use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   50use std::{
   51    iter,
   52    sync::atomic::{self, AtomicUsize},
   53};
   54use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   55use text::ToPoint as _;
   56use unindent::Unindent;
   57use util::{
   58    assert_set_eq, path,
   59    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   60    uri,
   61};
   62use workspace::{
   63    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   64    OpenOptions, ViewId,
   65    invalid_buffer_view::InvalidBufferView,
   66    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   67    register_project_item,
   68};
   69
   70#[gpui::test]
   71fn test_edit_events(cx: &mut TestAppContext) {
   72    init_test(cx, |_| {});
   73
   74    let buffer = cx.new(|cx| {
   75        let mut buffer = language::Buffer::local("123456", cx);
   76        buffer.set_group_interval(Duration::from_secs(1));
   77        buffer
   78    });
   79
   80    let events = Rc::new(RefCell::new(Vec::new()));
   81    let editor1 = cx.add_window({
   82        let events = events.clone();
   83        |window, cx| {
   84            let entity = cx.entity();
   85            cx.subscribe_in(
   86                &entity,
   87                window,
   88                move |_, _, event: &EditorEvent, _, _| match event {
   89                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   90                    EditorEvent::BufferEdited => {
   91                        events.borrow_mut().push(("editor1", "buffer edited"))
   92                    }
   93                    _ => {}
   94                },
   95            )
   96            .detach();
   97            Editor::for_buffer(buffer.clone(), None, window, cx)
   98        }
   99    });
  100
  101    let editor2 = cx.add_window({
  102        let events = events.clone();
  103        |window, cx| {
  104            cx.subscribe_in(
  105                &cx.entity(),
  106                window,
  107                move |_, _, event: &EditorEvent, _, _| match event {
  108                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  109                    EditorEvent::BufferEdited => {
  110                        events.borrow_mut().push(("editor2", "buffer edited"))
  111                    }
  112                    _ => {}
  113                },
  114            )
  115            .detach();
  116            Editor::for_buffer(buffer.clone(), None, window, cx)
  117        }
  118    });
  119
  120    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  121
  122    // Mutating editor 1 will emit an `Edited` event only for that editor.
  123    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  124    assert_eq!(
  125        mem::take(&mut *events.borrow_mut()),
  126        [
  127            ("editor1", "edited"),
  128            ("editor1", "buffer edited"),
  129            ("editor2", "buffer edited"),
  130        ]
  131    );
  132
  133    // Mutating editor 2 will emit an `Edited` event only for that editor.
  134    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  135    assert_eq!(
  136        mem::take(&mut *events.borrow_mut()),
  137        [
  138            ("editor2", "edited"),
  139            ("editor1", "buffer edited"),
  140            ("editor2", "buffer edited"),
  141        ]
  142    );
  143
  144    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  145    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  146    assert_eq!(
  147        mem::take(&mut *events.borrow_mut()),
  148        [
  149            ("editor1", "edited"),
  150            ("editor1", "buffer edited"),
  151            ("editor2", "buffer edited"),
  152        ]
  153    );
  154
  155    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  156    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  157    assert_eq!(
  158        mem::take(&mut *events.borrow_mut()),
  159        [
  160            ("editor1", "edited"),
  161            ("editor1", "buffer edited"),
  162            ("editor2", "buffer edited"),
  163        ]
  164    );
  165
  166    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  167    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  168    assert_eq!(
  169        mem::take(&mut *events.borrow_mut()),
  170        [
  171            ("editor2", "edited"),
  172            ("editor1", "buffer edited"),
  173            ("editor2", "buffer edited"),
  174        ]
  175    );
  176
  177    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  178    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  179    assert_eq!(
  180        mem::take(&mut *events.borrow_mut()),
  181        [
  182            ("editor2", "edited"),
  183            ("editor1", "buffer edited"),
  184            ("editor2", "buffer edited"),
  185        ]
  186    );
  187
  188    // No event is emitted when the mutation is a no-op.
  189    _ = editor2.update(cx, |editor, window, cx| {
  190        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  191            s.select_ranges([0..0])
  192        });
  193
  194        editor.backspace(&Backspace, window, cx);
  195    });
  196    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  197}
  198
  199#[gpui::test]
  200fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  201    init_test(cx, |_| {});
  202
  203    let mut now = Instant::now();
  204    let group_interval = Duration::from_millis(1);
  205    let buffer = cx.new(|cx| {
  206        let mut buf = language::Buffer::local("123456", cx);
  207        buf.set_group_interval(group_interval);
  208        buf
  209    });
  210    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  211    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  212
  213    _ = editor.update(cx, |editor, window, cx| {
  214        editor.start_transaction_at(now, window, cx);
  215        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  216            s.select_ranges([2..4])
  217        });
  218
  219        editor.insert("cd", window, cx);
  220        editor.end_transaction_at(now, cx);
  221        assert_eq!(editor.text(cx), "12cd56");
  222        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  223
  224        editor.start_transaction_at(now, window, cx);
  225        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  226            s.select_ranges([4..5])
  227        });
  228        editor.insert("e", window, cx);
  229        editor.end_transaction_at(now, cx);
  230        assert_eq!(editor.text(cx), "12cde6");
  231        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  232
  233        now += group_interval + Duration::from_millis(1);
  234        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  235            s.select_ranges([2..2])
  236        });
  237
  238        // Simulate an edit in another editor
  239        buffer.update(cx, |buffer, cx| {
  240            buffer.start_transaction_at(now, cx);
  241            buffer.edit([(0..1, "a")], None, cx);
  242            buffer.edit([(1..1, "b")], None, cx);
  243            buffer.end_transaction_at(now, cx);
  244        });
  245
  246        assert_eq!(editor.text(cx), "ab2cde6");
  247        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  248
  249        // Last transaction happened past the group interval in a different editor.
  250        // Undo it individually and don't restore selections.
  251        editor.undo(&Undo, window, cx);
  252        assert_eq!(editor.text(cx), "12cde6");
  253        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  254
  255        // First two transactions happened within the group interval in this editor.
  256        // Undo them together and restore selections.
  257        editor.undo(&Undo, window, cx);
  258        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  259        assert_eq!(editor.text(cx), "123456");
  260        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  261
  262        // Redo the first two transactions together.
  263        editor.redo(&Redo, window, cx);
  264        assert_eq!(editor.text(cx), "12cde6");
  265        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  266
  267        // Redo the last transaction on its own.
  268        editor.redo(&Redo, window, cx);
  269        assert_eq!(editor.text(cx), "ab2cde6");
  270        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  271
  272        // Test empty transactions.
  273        editor.start_transaction_at(now, window, cx);
  274        editor.end_transaction_at(now, cx);
  275        editor.undo(&Undo, window, cx);
  276        assert_eq!(editor.text(cx), "12cde6");
  277    });
  278}
  279
  280#[gpui::test]
  281fn test_ime_composition(cx: &mut TestAppContext) {
  282    init_test(cx, |_| {});
  283
  284    let buffer = cx.new(|cx| {
  285        let mut buffer = language::Buffer::local("abcde", cx);
  286        // Ensure automatic grouping doesn't occur.
  287        buffer.set_group_interval(Duration::ZERO);
  288        buffer
  289    });
  290
  291    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  292    cx.add_window(|window, cx| {
  293        let mut editor = build_editor(buffer.clone(), window, cx);
  294
  295        // Start a new IME composition.
  296        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  297        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  298        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  299        assert_eq!(editor.text(cx), "äbcde");
  300        assert_eq!(
  301            editor.marked_text_ranges(cx),
  302            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  303        );
  304
  305        // Finalize IME composition.
  306        editor.replace_text_in_range(None, "ā", window, cx);
  307        assert_eq!(editor.text(cx), "ābcde");
  308        assert_eq!(editor.marked_text_ranges(cx), None);
  309
  310        // IME composition edits are grouped and are undone/redone at once.
  311        editor.undo(&Default::default(), window, cx);
  312        assert_eq!(editor.text(cx), "abcde");
  313        assert_eq!(editor.marked_text_ranges(cx), None);
  314        editor.redo(&Default::default(), window, cx);
  315        assert_eq!(editor.text(cx), "ābcde");
  316        assert_eq!(editor.marked_text_ranges(cx), None);
  317
  318        // Start a new IME composition.
  319        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  320        assert_eq!(
  321            editor.marked_text_ranges(cx),
  322            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  323        );
  324
  325        // Undoing during an IME composition cancels it.
  326        editor.undo(&Default::default(), window, cx);
  327        assert_eq!(editor.text(cx), "ābcde");
  328        assert_eq!(editor.marked_text_ranges(cx), None);
  329
  330        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  331        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  332        assert_eq!(editor.text(cx), "ābcdè");
  333        assert_eq!(
  334            editor.marked_text_ranges(cx),
  335            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  336        );
  337
  338        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  339        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  340        assert_eq!(editor.text(cx), "ābcdę");
  341        assert_eq!(editor.marked_text_ranges(cx), None);
  342
  343        // Start a new IME composition with multiple cursors.
  344        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  345            s.select_ranges([
  346                OffsetUtf16(1)..OffsetUtf16(1),
  347                OffsetUtf16(3)..OffsetUtf16(3),
  348                OffsetUtf16(5)..OffsetUtf16(5),
  349            ])
  350        });
  351        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  352        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  353        assert_eq!(
  354            editor.marked_text_ranges(cx),
  355            Some(vec![
  356                OffsetUtf16(0)..OffsetUtf16(3),
  357                OffsetUtf16(4)..OffsetUtf16(7),
  358                OffsetUtf16(8)..OffsetUtf16(11)
  359            ])
  360        );
  361
  362        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  363        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  364        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  365        assert_eq!(
  366            editor.marked_text_ranges(cx),
  367            Some(vec![
  368                OffsetUtf16(1)..OffsetUtf16(2),
  369                OffsetUtf16(5)..OffsetUtf16(6),
  370                OffsetUtf16(9)..OffsetUtf16(10)
  371            ])
  372        );
  373
  374        // Finalize IME composition with multiple cursors.
  375        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  376        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  377        assert_eq!(editor.marked_text_ranges(cx), None);
  378
  379        editor
  380    });
  381}
  382
  383#[gpui::test]
  384fn test_selection_with_mouse(cx: &mut TestAppContext) {
  385    init_test(cx, |_| {});
  386
  387    let editor = cx.add_window(|window, cx| {
  388        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  389        build_editor(buffer, window, cx)
  390    });
  391
  392    _ = editor.update(cx, |editor, window, cx| {
  393        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  394    });
  395    assert_eq!(
  396        editor
  397            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  398            .unwrap(),
  399        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  400    );
  401
  402    _ = editor.update(cx, |editor, window, cx| {
  403        editor.update_selection(
  404            DisplayPoint::new(DisplayRow(3), 3),
  405            0,
  406            gpui::Point::<f32>::default(),
  407            window,
  408            cx,
  409        );
  410    });
  411
  412    assert_eq!(
  413        editor
  414            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  415            .unwrap(),
  416        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  417    );
  418
  419    _ = editor.update(cx, |editor, window, cx| {
  420        editor.update_selection(
  421            DisplayPoint::new(DisplayRow(1), 1),
  422            0,
  423            gpui::Point::<f32>::default(),
  424            window,
  425            cx,
  426        );
  427    });
  428
  429    assert_eq!(
  430        editor
  431            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  432            .unwrap(),
  433        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  434    );
  435
  436    _ = editor.update(cx, |editor, window, cx| {
  437        editor.end_selection(window, cx);
  438        editor.update_selection(
  439            DisplayPoint::new(DisplayRow(3), 3),
  440            0,
  441            gpui::Point::<f32>::default(),
  442            window,
  443            cx,
  444        );
  445    });
  446
  447    assert_eq!(
  448        editor
  449            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  450            .unwrap(),
  451        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  452    );
  453
  454    _ = editor.update(cx, |editor, window, cx| {
  455        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  456        editor.update_selection(
  457            DisplayPoint::new(DisplayRow(0), 0),
  458            0,
  459            gpui::Point::<f32>::default(),
  460            window,
  461            cx,
  462        );
  463    });
  464
  465    assert_eq!(
  466        editor
  467            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  468            .unwrap(),
  469        [
  470            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  471            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  472        ]
  473    );
  474
  475    _ = editor.update(cx, |editor, window, cx| {
  476        editor.end_selection(window, cx);
  477    });
  478
  479    assert_eq!(
  480        editor
  481            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  482            .unwrap(),
  483        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  484    );
  485}
  486
  487#[gpui::test]
  488fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  489    init_test(cx, |_| {});
  490
  491    let editor = cx.add_window(|window, cx| {
  492        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  493        build_editor(buffer, window, cx)
  494    });
  495
  496    _ = editor.update(cx, |editor, window, cx| {
  497        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  498    });
  499
  500    _ = editor.update(cx, |editor, window, cx| {
  501        editor.end_selection(window, cx);
  502    });
  503
  504    _ = editor.update(cx, |editor, window, cx| {
  505        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  506    });
  507
  508    _ = editor.update(cx, |editor, window, cx| {
  509        editor.end_selection(window, cx);
  510    });
  511
  512    assert_eq!(
  513        editor
  514            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  515            .unwrap(),
  516        [
  517            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  518            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  519        ]
  520    );
  521
  522    _ = editor.update(cx, |editor, window, cx| {
  523        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  524    });
  525
  526    _ = editor.update(cx, |editor, window, cx| {
  527        editor.end_selection(window, cx);
  528    });
  529
  530    assert_eq!(
  531        editor
  532            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  533            .unwrap(),
  534        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  535    );
  536}
  537
  538#[gpui::test]
  539fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  540    init_test(cx, |_| {});
  541
  542    let editor = cx.add_window(|window, cx| {
  543        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  544        build_editor(buffer, window, cx)
  545    });
  546
  547    _ = editor.update(cx, |editor, window, cx| {
  548        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  549        assert_eq!(
  550            editor.selections.display_ranges(cx),
  551            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  552        );
  553    });
  554
  555    _ = editor.update(cx, |editor, window, cx| {
  556        editor.update_selection(
  557            DisplayPoint::new(DisplayRow(3), 3),
  558            0,
  559            gpui::Point::<f32>::default(),
  560            window,
  561            cx,
  562        );
  563        assert_eq!(
  564            editor.selections.display_ranges(cx),
  565            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  566        );
  567    });
  568
  569    _ = editor.update(cx, |editor, window, cx| {
  570        editor.cancel(&Cancel, window, cx);
  571        editor.update_selection(
  572            DisplayPoint::new(DisplayRow(1), 1),
  573            0,
  574            gpui::Point::<f32>::default(),
  575            window,
  576            cx,
  577        );
  578        assert_eq!(
  579            editor.selections.display_ranges(cx),
  580            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  581        );
  582    });
  583}
  584
  585#[gpui::test]
  586fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  587    init_test(cx, |_| {});
  588
  589    let editor = cx.add_window(|window, cx| {
  590        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  591        build_editor(buffer, window, cx)
  592    });
  593
  594    _ = editor.update(cx, |editor, window, cx| {
  595        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  596        assert_eq!(
  597            editor.selections.display_ranges(cx),
  598            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  599        );
  600
  601        editor.move_down(&Default::default(), window, cx);
  602        assert_eq!(
  603            editor.selections.display_ranges(cx),
  604            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  605        );
  606
  607        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  608        assert_eq!(
  609            editor.selections.display_ranges(cx),
  610            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  611        );
  612
  613        editor.move_up(&Default::default(), window, cx);
  614        assert_eq!(
  615            editor.selections.display_ranges(cx),
  616            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  617        );
  618    });
  619}
  620
  621#[gpui::test]
  622fn test_clone(cx: &mut TestAppContext) {
  623    init_test(cx, |_| {});
  624
  625    let (text, selection_ranges) = marked_text_ranges(
  626        indoc! {"
  627            one
  628            two
  629            threeˇ
  630            four
  631            fiveˇ
  632        "},
  633        true,
  634    );
  635
  636    let editor = cx.add_window(|window, cx| {
  637        let buffer = MultiBuffer::build_simple(&text, cx);
  638        build_editor(buffer, window, cx)
  639    });
  640
  641    _ = editor.update(cx, |editor, window, cx| {
  642        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  643            s.select_ranges(selection_ranges.clone())
  644        });
  645        editor.fold_creases(
  646            vec![
  647                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  648                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  649            ],
  650            true,
  651            window,
  652            cx,
  653        );
  654    });
  655
  656    let cloned_editor = editor
  657        .update(cx, |editor, _, cx| {
  658            cx.open_window(Default::default(), |window, cx| {
  659                cx.new(|cx| editor.clone(window, cx))
  660            })
  661        })
  662        .unwrap()
  663        .unwrap();
  664
  665    let snapshot = editor
  666        .update(cx, |e, window, cx| e.snapshot(window, cx))
  667        .unwrap();
  668    let cloned_snapshot = cloned_editor
  669        .update(cx, |e, window, cx| e.snapshot(window, cx))
  670        .unwrap();
  671
  672    assert_eq!(
  673        cloned_editor
  674            .update(cx, |e, _, cx| e.display_text(cx))
  675            .unwrap(),
  676        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  677    );
  678    assert_eq!(
  679        cloned_snapshot
  680            .folds_in_range(0..text.len())
  681            .collect::<Vec<_>>(),
  682        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  683    );
  684    assert_set_eq!(
  685        cloned_editor
  686            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  687            .unwrap(),
  688        editor
  689            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  690            .unwrap()
  691    );
  692    assert_set_eq!(
  693        cloned_editor
  694            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  695            .unwrap(),
  696        editor
  697            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  698            .unwrap()
  699    );
  700}
  701
  702#[gpui::test]
  703async fn test_navigation_history(cx: &mut TestAppContext) {
  704    init_test(cx, |_| {});
  705
  706    use workspace::item::Item;
  707
  708    let fs = FakeFs::new(cx.executor());
  709    let project = Project::test(fs, [], cx).await;
  710    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  711    let pane = workspace
  712        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  713        .unwrap();
  714
  715    _ = workspace.update(cx, |_v, window, cx| {
  716        cx.new(|cx| {
  717            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  718            let mut editor = build_editor(buffer, window, cx);
  719            let handle = cx.entity();
  720            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  721
  722            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  723                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  724            }
  725
  726            // Move the cursor a small distance.
  727            // Nothing is added to the navigation history.
  728            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  729                s.select_display_ranges([
  730                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  731                ])
  732            });
  733            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  734                s.select_display_ranges([
  735                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  736                ])
  737            });
  738            assert!(pop_history(&mut editor, cx).is_none());
  739
  740            // Move the cursor a large distance.
  741            // The history can jump back to the previous position.
  742            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  743                s.select_display_ranges([
  744                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  745                ])
  746            });
  747            let nav_entry = pop_history(&mut editor, cx).unwrap();
  748            editor.navigate(nav_entry.data.unwrap(), window, cx);
  749            assert_eq!(nav_entry.item.id(), cx.entity_id());
  750            assert_eq!(
  751                editor.selections.display_ranges(cx),
  752                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  753            );
  754            assert!(pop_history(&mut editor, cx).is_none());
  755
  756            // Move the cursor a small distance via the mouse.
  757            // Nothing is added to the navigation history.
  758            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  759            editor.end_selection(window, cx);
  760            assert_eq!(
  761                editor.selections.display_ranges(cx),
  762                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  763            );
  764            assert!(pop_history(&mut editor, cx).is_none());
  765
  766            // Move the cursor a large distance via the mouse.
  767            // The history can jump back to the previous position.
  768            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  769            editor.end_selection(window, cx);
  770            assert_eq!(
  771                editor.selections.display_ranges(cx),
  772                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  773            );
  774            let nav_entry = pop_history(&mut editor, cx).unwrap();
  775            editor.navigate(nav_entry.data.unwrap(), window, cx);
  776            assert_eq!(nav_entry.item.id(), cx.entity_id());
  777            assert_eq!(
  778                editor.selections.display_ranges(cx),
  779                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  780            );
  781            assert!(pop_history(&mut editor, cx).is_none());
  782
  783            // Set scroll position to check later
  784            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  785            let original_scroll_position = editor.scroll_manager.anchor();
  786
  787            // Jump to the end of the document and adjust scroll
  788            editor.move_to_end(&MoveToEnd, window, cx);
  789            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  790            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  791
  792            let nav_entry = pop_history(&mut editor, cx).unwrap();
  793            editor.navigate(nav_entry.data.unwrap(), window, cx);
  794            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  795
  796            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  797            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  798            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  799            let invalid_point = Point::new(9999, 0);
  800            editor.navigate(
  801                Box::new(NavigationData {
  802                    cursor_anchor: invalid_anchor,
  803                    cursor_position: invalid_point,
  804                    scroll_anchor: ScrollAnchor {
  805                        anchor: invalid_anchor,
  806                        offset: Default::default(),
  807                    },
  808                    scroll_top_row: invalid_point.row,
  809                }),
  810                window,
  811                cx,
  812            );
  813            assert_eq!(
  814                editor.selections.display_ranges(cx),
  815                &[editor.max_point(cx)..editor.max_point(cx)]
  816            );
  817            assert_eq!(
  818                editor.scroll_position(cx),
  819                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  820            );
  821
  822            editor
  823        })
  824    });
  825}
  826
  827#[gpui::test]
  828fn test_cancel(cx: &mut TestAppContext) {
  829    init_test(cx, |_| {});
  830
  831    let editor = cx.add_window(|window, cx| {
  832        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  833        build_editor(buffer, window, cx)
  834    });
  835
  836    _ = editor.update(cx, |editor, window, cx| {
  837        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  838        editor.update_selection(
  839            DisplayPoint::new(DisplayRow(1), 1),
  840            0,
  841            gpui::Point::<f32>::default(),
  842            window,
  843            cx,
  844        );
  845        editor.end_selection(window, cx);
  846
  847        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  848        editor.update_selection(
  849            DisplayPoint::new(DisplayRow(0), 3),
  850            0,
  851            gpui::Point::<f32>::default(),
  852            window,
  853            cx,
  854        );
  855        editor.end_selection(window, cx);
  856        assert_eq!(
  857            editor.selections.display_ranges(cx),
  858            [
  859                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  860                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  861            ]
  862        );
  863    });
  864
  865    _ = editor.update(cx, |editor, window, cx| {
  866        editor.cancel(&Cancel, window, cx);
  867        assert_eq!(
  868            editor.selections.display_ranges(cx),
  869            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  870        );
  871    });
  872
  873    _ = editor.update(cx, |editor, window, cx| {
  874        editor.cancel(&Cancel, window, cx);
  875        assert_eq!(
  876            editor.selections.display_ranges(cx),
  877            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  878        );
  879    });
  880}
  881
  882#[gpui::test]
  883fn test_fold_action(cx: &mut TestAppContext) {
  884    init_test(cx, |_| {});
  885
  886    let editor = cx.add_window(|window, cx| {
  887        let buffer = MultiBuffer::build_simple(
  888            &"
  889                impl Foo {
  890                    // Hello!
  891
  892                    fn a() {
  893                        1
  894                    }
  895
  896                    fn b() {
  897                        2
  898                    }
  899
  900                    fn c() {
  901                        3
  902                    }
  903                }
  904            "
  905            .unindent(),
  906            cx,
  907        );
  908        build_editor(buffer, window, cx)
  909    });
  910
  911    _ = editor.update(cx, |editor, window, cx| {
  912        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  913            s.select_display_ranges([
  914                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  915            ]);
  916        });
  917        editor.fold(&Fold, window, cx);
  918        assert_eq!(
  919            editor.display_text(cx),
  920            "
  921                impl Foo {
  922                    // Hello!
  923
  924                    fn a() {
  925                        1
  926                    }
  927
  928                    fn b() {⋯
  929                    }
  930
  931                    fn c() {⋯
  932                    }
  933                }
  934            "
  935            .unindent(),
  936        );
  937
  938        editor.fold(&Fold, window, cx);
  939        assert_eq!(
  940            editor.display_text(cx),
  941            "
  942                impl Foo {⋯
  943                }
  944            "
  945            .unindent(),
  946        );
  947
  948        editor.unfold_lines(&UnfoldLines, window, cx);
  949        assert_eq!(
  950            editor.display_text(cx),
  951            "
  952                impl Foo {
  953                    // Hello!
  954
  955                    fn a() {
  956                        1
  957                    }
  958
  959                    fn b() {⋯
  960                    }
  961
  962                    fn c() {⋯
  963                    }
  964                }
  965            "
  966            .unindent(),
  967        );
  968
  969        editor.unfold_lines(&UnfoldLines, window, cx);
  970        assert_eq!(
  971            editor.display_text(cx),
  972            editor.buffer.read(cx).read(cx).text()
  973        );
  974    });
  975}
  976
  977#[gpui::test]
  978fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  979    init_test(cx, |_| {});
  980
  981    let editor = cx.add_window(|window, cx| {
  982        let buffer = MultiBuffer::build_simple(
  983            &"
  984                class Foo:
  985                    # Hello!
  986
  987                    def a():
  988                        print(1)
  989
  990                    def b():
  991                        print(2)
  992
  993                    def c():
  994                        print(3)
  995            "
  996            .unindent(),
  997            cx,
  998        );
  999        build_editor(buffer, window, cx)
 1000    });
 1001
 1002    _ = editor.update(cx, |editor, window, cx| {
 1003        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1004            s.select_display_ranges([
 1005                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1006            ]);
 1007        });
 1008        editor.fold(&Fold, window, cx);
 1009        assert_eq!(
 1010            editor.display_text(cx),
 1011            "
 1012                class Foo:
 1013                    # Hello!
 1014
 1015                    def a():
 1016                        print(1)
 1017
 1018                    def b():⋯
 1019
 1020                    def c():⋯
 1021            "
 1022            .unindent(),
 1023        );
 1024
 1025        editor.fold(&Fold, window, cx);
 1026        assert_eq!(
 1027            editor.display_text(cx),
 1028            "
 1029                class Foo:⋯
 1030            "
 1031            .unindent(),
 1032        );
 1033
 1034        editor.unfold_lines(&UnfoldLines, window, cx);
 1035        assert_eq!(
 1036            editor.display_text(cx),
 1037            "
 1038                class Foo:
 1039                    # Hello!
 1040
 1041                    def a():
 1042                        print(1)
 1043
 1044                    def b():⋯
 1045
 1046                    def c():⋯
 1047            "
 1048            .unindent(),
 1049        );
 1050
 1051        editor.unfold_lines(&UnfoldLines, window, cx);
 1052        assert_eq!(
 1053            editor.display_text(cx),
 1054            editor.buffer.read(cx).read(cx).text()
 1055        );
 1056    });
 1057}
 1058
 1059#[gpui::test]
 1060fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1061    init_test(cx, |_| {});
 1062
 1063    let editor = cx.add_window(|window, cx| {
 1064        let buffer = MultiBuffer::build_simple(
 1065            &"
 1066                class Foo:
 1067                    # Hello!
 1068
 1069                    def a():
 1070                        print(1)
 1071
 1072                    def b():
 1073                        print(2)
 1074
 1075
 1076                    def c():
 1077                        print(3)
 1078
 1079
 1080            "
 1081            .unindent(),
 1082            cx,
 1083        );
 1084        build_editor(buffer, window, cx)
 1085    });
 1086
 1087    _ = editor.update(cx, |editor, window, cx| {
 1088        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1089            s.select_display_ranges([
 1090                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1091            ]);
 1092        });
 1093        editor.fold(&Fold, window, cx);
 1094        assert_eq!(
 1095            editor.display_text(cx),
 1096            "
 1097                class Foo:
 1098                    # Hello!
 1099
 1100                    def a():
 1101                        print(1)
 1102
 1103                    def b():⋯
 1104
 1105
 1106                    def c():⋯
 1107
 1108
 1109            "
 1110            .unindent(),
 1111        );
 1112
 1113        editor.fold(&Fold, window, cx);
 1114        assert_eq!(
 1115            editor.display_text(cx),
 1116            "
 1117                class Foo:⋯
 1118
 1119
 1120            "
 1121            .unindent(),
 1122        );
 1123
 1124        editor.unfold_lines(&UnfoldLines, window, cx);
 1125        assert_eq!(
 1126            editor.display_text(cx),
 1127            "
 1128                class Foo:
 1129                    # Hello!
 1130
 1131                    def a():
 1132                        print(1)
 1133
 1134                    def b():⋯
 1135
 1136
 1137                    def c():⋯
 1138
 1139
 1140            "
 1141            .unindent(),
 1142        );
 1143
 1144        editor.unfold_lines(&UnfoldLines, window, cx);
 1145        assert_eq!(
 1146            editor.display_text(cx),
 1147            editor.buffer.read(cx).read(cx).text()
 1148        );
 1149    });
 1150}
 1151
 1152#[gpui::test]
 1153fn test_fold_at_level(cx: &mut TestAppContext) {
 1154    init_test(cx, |_| {});
 1155
 1156    let editor = cx.add_window(|window, cx| {
 1157        let buffer = MultiBuffer::build_simple(
 1158            &"
 1159                class Foo:
 1160                    # Hello!
 1161
 1162                    def a():
 1163                        print(1)
 1164
 1165                    def b():
 1166                        print(2)
 1167
 1168
 1169                class Bar:
 1170                    # World!
 1171
 1172                    def a():
 1173                        print(1)
 1174
 1175                    def b():
 1176                        print(2)
 1177
 1178
 1179            "
 1180            .unindent(),
 1181            cx,
 1182        );
 1183        build_editor(buffer, window, cx)
 1184    });
 1185
 1186    _ = editor.update(cx, |editor, window, cx| {
 1187        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1188        assert_eq!(
 1189            editor.display_text(cx),
 1190            "
 1191                class Foo:
 1192                    # Hello!
 1193
 1194                    def a():⋯
 1195
 1196                    def b():⋯
 1197
 1198
 1199                class Bar:
 1200                    # World!
 1201
 1202                    def a():⋯
 1203
 1204                    def b():⋯
 1205
 1206
 1207            "
 1208            .unindent(),
 1209        );
 1210
 1211        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1212        assert_eq!(
 1213            editor.display_text(cx),
 1214            "
 1215                class Foo:⋯
 1216
 1217
 1218                class Bar:⋯
 1219
 1220
 1221            "
 1222            .unindent(),
 1223        );
 1224
 1225        editor.unfold_all(&UnfoldAll, window, cx);
 1226        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1227        assert_eq!(
 1228            editor.display_text(cx),
 1229            "
 1230                class Foo:
 1231                    # Hello!
 1232
 1233                    def a():
 1234                        print(1)
 1235
 1236                    def b():
 1237                        print(2)
 1238
 1239
 1240                class Bar:
 1241                    # World!
 1242
 1243                    def a():
 1244                        print(1)
 1245
 1246                    def b():
 1247                        print(2)
 1248
 1249
 1250            "
 1251            .unindent(),
 1252        );
 1253
 1254        assert_eq!(
 1255            editor.display_text(cx),
 1256            editor.buffer.read(cx).read(cx).text()
 1257        );
 1258    });
 1259}
 1260
 1261#[gpui::test]
 1262fn test_move_cursor(cx: &mut TestAppContext) {
 1263    init_test(cx, |_| {});
 1264
 1265    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1266    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1267
 1268    buffer.update(cx, |buffer, cx| {
 1269        buffer.edit(
 1270            vec![
 1271                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1272                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1273            ],
 1274            None,
 1275            cx,
 1276        );
 1277    });
 1278    _ = editor.update(cx, |editor, window, cx| {
 1279        assert_eq!(
 1280            editor.selections.display_ranges(cx),
 1281            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1282        );
 1283
 1284        editor.move_down(&MoveDown, window, cx);
 1285        assert_eq!(
 1286            editor.selections.display_ranges(cx),
 1287            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1288        );
 1289
 1290        editor.move_right(&MoveRight, window, cx);
 1291        assert_eq!(
 1292            editor.selections.display_ranges(cx),
 1293            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1294        );
 1295
 1296        editor.move_left(&MoveLeft, window, cx);
 1297        assert_eq!(
 1298            editor.selections.display_ranges(cx),
 1299            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1300        );
 1301
 1302        editor.move_up(&MoveUp, window, cx);
 1303        assert_eq!(
 1304            editor.selections.display_ranges(cx),
 1305            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1306        );
 1307
 1308        editor.move_to_end(&MoveToEnd, window, cx);
 1309        assert_eq!(
 1310            editor.selections.display_ranges(cx),
 1311            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1312        );
 1313
 1314        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1315        assert_eq!(
 1316            editor.selections.display_ranges(cx),
 1317            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1318        );
 1319
 1320        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1321            s.select_display_ranges([
 1322                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1323            ]);
 1324        });
 1325        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1326        assert_eq!(
 1327            editor.selections.display_ranges(cx),
 1328            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1329        );
 1330
 1331        editor.select_to_end(&SelectToEnd, window, cx);
 1332        assert_eq!(
 1333            editor.selections.display_ranges(cx),
 1334            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1335        );
 1336    });
 1337}
 1338
 1339#[gpui::test]
 1340fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1341    init_test(cx, |_| {});
 1342
 1343    let editor = cx.add_window(|window, cx| {
 1344        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1345        build_editor(buffer, window, cx)
 1346    });
 1347
 1348    assert_eq!('🟥'.len_utf8(), 4);
 1349    assert_eq!('α'.len_utf8(), 2);
 1350
 1351    _ = editor.update(cx, |editor, window, cx| {
 1352        editor.fold_creases(
 1353            vec![
 1354                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1355                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1356                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1357            ],
 1358            true,
 1359            window,
 1360            cx,
 1361        );
 1362        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1363
 1364        editor.move_right(&MoveRight, window, cx);
 1365        assert_eq!(
 1366            editor.selections.display_ranges(cx),
 1367            &[empty_range(0, "🟥".len())]
 1368        );
 1369        editor.move_right(&MoveRight, window, cx);
 1370        assert_eq!(
 1371            editor.selections.display_ranges(cx),
 1372            &[empty_range(0, "🟥🟧".len())]
 1373        );
 1374        editor.move_right(&MoveRight, window, cx);
 1375        assert_eq!(
 1376            editor.selections.display_ranges(cx),
 1377            &[empty_range(0, "🟥🟧⋯".len())]
 1378        );
 1379
 1380        editor.move_down(&MoveDown, window, cx);
 1381        assert_eq!(
 1382            editor.selections.display_ranges(cx),
 1383            &[empty_range(1, "ab⋯e".len())]
 1384        );
 1385        editor.move_left(&MoveLeft, window, cx);
 1386        assert_eq!(
 1387            editor.selections.display_ranges(cx),
 1388            &[empty_range(1, "ab⋯".len())]
 1389        );
 1390        editor.move_left(&MoveLeft, window, cx);
 1391        assert_eq!(
 1392            editor.selections.display_ranges(cx),
 1393            &[empty_range(1, "ab".len())]
 1394        );
 1395        editor.move_left(&MoveLeft, window, cx);
 1396        assert_eq!(
 1397            editor.selections.display_ranges(cx),
 1398            &[empty_range(1, "a".len())]
 1399        );
 1400
 1401        editor.move_down(&MoveDown, window, cx);
 1402        assert_eq!(
 1403            editor.selections.display_ranges(cx),
 1404            &[empty_range(2, "α".len())]
 1405        );
 1406        editor.move_right(&MoveRight, window, cx);
 1407        assert_eq!(
 1408            editor.selections.display_ranges(cx),
 1409            &[empty_range(2, "αβ".len())]
 1410        );
 1411        editor.move_right(&MoveRight, window, cx);
 1412        assert_eq!(
 1413            editor.selections.display_ranges(cx),
 1414            &[empty_range(2, "αβ⋯".len())]
 1415        );
 1416        editor.move_right(&MoveRight, window, cx);
 1417        assert_eq!(
 1418            editor.selections.display_ranges(cx),
 1419            &[empty_range(2, "αβ⋯ε".len())]
 1420        );
 1421
 1422        editor.move_up(&MoveUp, window, cx);
 1423        assert_eq!(
 1424            editor.selections.display_ranges(cx),
 1425            &[empty_range(1, "ab⋯e".len())]
 1426        );
 1427        editor.move_down(&MoveDown, window, cx);
 1428        assert_eq!(
 1429            editor.selections.display_ranges(cx),
 1430            &[empty_range(2, "αβ⋯ε".len())]
 1431        );
 1432        editor.move_up(&MoveUp, window, cx);
 1433        assert_eq!(
 1434            editor.selections.display_ranges(cx),
 1435            &[empty_range(1, "ab⋯e".len())]
 1436        );
 1437
 1438        editor.move_up(&MoveUp, window, cx);
 1439        assert_eq!(
 1440            editor.selections.display_ranges(cx),
 1441            &[empty_range(0, "🟥🟧".len())]
 1442        );
 1443        editor.move_left(&MoveLeft, window, cx);
 1444        assert_eq!(
 1445            editor.selections.display_ranges(cx),
 1446            &[empty_range(0, "🟥".len())]
 1447        );
 1448        editor.move_left(&MoveLeft, window, cx);
 1449        assert_eq!(
 1450            editor.selections.display_ranges(cx),
 1451            &[empty_range(0, "".len())]
 1452        );
 1453    });
 1454}
 1455
 1456#[gpui::test]
 1457fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1458    init_test(cx, |_| {});
 1459
 1460    let editor = cx.add_window(|window, cx| {
 1461        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1462        build_editor(buffer, window, cx)
 1463    });
 1464    _ = editor.update(cx, |editor, window, cx| {
 1465        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1466            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1467        });
 1468
 1469        // moving above start of document should move selection to start of document,
 1470        // but the next move down should still be at the original goal_x
 1471        editor.move_up(&MoveUp, window, cx);
 1472        assert_eq!(
 1473            editor.selections.display_ranges(cx),
 1474            &[empty_range(0, "".len())]
 1475        );
 1476
 1477        editor.move_down(&MoveDown, window, cx);
 1478        assert_eq!(
 1479            editor.selections.display_ranges(cx),
 1480            &[empty_range(1, "abcd".len())]
 1481        );
 1482
 1483        editor.move_down(&MoveDown, window, cx);
 1484        assert_eq!(
 1485            editor.selections.display_ranges(cx),
 1486            &[empty_range(2, "αβγ".len())]
 1487        );
 1488
 1489        editor.move_down(&MoveDown, window, cx);
 1490        assert_eq!(
 1491            editor.selections.display_ranges(cx),
 1492            &[empty_range(3, "abcd".len())]
 1493        );
 1494
 1495        editor.move_down(&MoveDown, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1499        );
 1500
 1501        // moving past end of document should not change goal_x
 1502        editor.move_down(&MoveDown, window, cx);
 1503        assert_eq!(
 1504            editor.selections.display_ranges(cx),
 1505            &[empty_range(5, "".len())]
 1506        );
 1507
 1508        editor.move_down(&MoveDown, window, cx);
 1509        assert_eq!(
 1510            editor.selections.display_ranges(cx),
 1511            &[empty_range(5, "".len())]
 1512        );
 1513
 1514        editor.move_up(&MoveUp, window, cx);
 1515        assert_eq!(
 1516            editor.selections.display_ranges(cx),
 1517            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1518        );
 1519
 1520        editor.move_up(&MoveUp, window, cx);
 1521        assert_eq!(
 1522            editor.selections.display_ranges(cx),
 1523            &[empty_range(3, "abcd".len())]
 1524        );
 1525
 1526        editor.move_up(&MoveUp, window, cx);
 1527        assert_eq!(
 1528            editor.selections.display_ranges(cx),
 1529            &[empty_range(2, "αβγ".len())]
 1530        );
 1531    });
 1532}
 1533
 1534#[gpui::test]
 1535fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1536    init_test(cx, |_| {});
 1537    let move_to_beg = MoveToBeginningOfLine {
 1538        stop_at_soft_wraps: true,
 1539        stop_at_indent: true,
 1540    };
 1541
 1542    let delete_to_beg = DeleteToBeginningOfLine {
 1543        stop_at_indent: false,
 1544    };
 1545
 1546    let move_to_end = MoveToEndOfLine {
 1547        stop_at_soft_wraps: true,
 1548    };
 1549
 1550    let editor = cx.add_window(|window, cx| {
 1551        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1552        build_editor(buffer, window, cx)
 1553    });
 1554    _ = editor.update(cx, |editor, window, cx| {
 1555        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1556            s.select_display_ranges([
 1557                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1558                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1559            ]);
 1560        });
 1561    });
 1562
 1563    _ = editor.update(cx, |editor, window, cx| {
 1564        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1565        assert_eq!(
 1566            editor.selections.display_ranges(cx),
 1567            &[
 1568                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1569                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1570            ]
 1571        );
 1572    });
 1573
 1574    _ = editor.update(cx, |editor, window, cx| {
 1575        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1576        assert_eq!(
 1577            editor.selections.display_ranges(cx),
 1578            &[
 1579                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1580                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1581            ]
 1582        );
 1583    });
 1584
 1585    _ = editor.update(cx, |editor, window, cx| {
 1586        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1587        assert_eq!(
 1588            editor.selections.display_ranges(cx),
 1589            &[
 1590                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1591                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1592            ]
 1593        );
 1594    });
 1595
 1596    _ = editor.update(cx, |editor, window, cx| {
 1597        editor.move_to_end_of_line(&move_to_end, window, cx);
 1598        assert_eq!(
 1599            editor.selections.display_ranges(cx),
 1600            &[
 1601                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1602                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1603            ]
 1604        );
 1605    });
 1606
 1607    // Moving to the end of line again is a no-op.
 1608    _ = editor.update(cx, |editor, window, cx| {
 1609        editor.move_to_end_of_line(&move_to_end, window, cx);
 1610        assert_eq!(
 1611            editor.selections.display_ranges(cx),
 1612            &[
 1613                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1614                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1615            ]
 1616        );
 1617    });
 1618
 1619    _ = editor.update(cx, |editor, window, cx| {
 1620        editor.move_left(&MoveLeft, window, cx);
 1621        editor.select_to_beginning_of_line(
 1622            &SelectToBeginningOfLine {
 1623                stop_at_soft_wraps: true,
 1624                stop_at_indent: true,
 1625            },
 1626            window,
 1627            cx,
 1628        );
 1629        assert_eq!(
 1630            editor.selections.display_ranges(cx),
 1631            &[
 1632                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1633                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1634            ]
 1635        );
 1636    });
 1637
 1638    _ = editor.update(cx, |editor, window, cx| {
 1639        editor.select_to_beginning_of_line(
 1640            &SelectToBeginningOfLine {
 1641                stop_at_soft_wraps: true,
 1642                stop_at_indent: true,
 1643            },
 1644            window,
 1645            cx,
 1646        );
 1647        assert_eq!(
 1648            editor.selections.display_ranges(cx),
 1649            &[
 1650                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1651                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1652            ]
 1653        );
 1654    });
 1655
 1656    _ = editor.update(cx, |editor, window, cx| {
 1657        editor.select_to_beginning_of_line(
 1658            &SelectToBeginningOfLine {
 1659                stop_at_soft_wraps: true,
 1660                stop_at_indent: true,
 1661            },
 1662            window,
 1663            cx,
 1664        );
 1665        assert_eq!(
 1666            editor.selections.display_ranges(cx),
 1667            &[
 1668                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1669                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1670            ]
 1671        );
 1672    });
 1673
 1674    _ = editor.update(cx, |editor, window, cx| {
 1675        editor.select_to_end_of_line(
 1676            &SelectToEndOfLine {
 1677                stop_at_soft_wraps: true,
 1678            },
 1679            window,
 1680            cx,
 1681        );
 1682        assert_eq!(
 1683            editor.selections.display_ranges(cx),
 1684            &[
 1685                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1686                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1687            ]
 1688        );
 1689    });
 1690
 1691    _ = editor.update(cx, |editor, window, cx| {
 1692        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1693        assert_eq!(editor.display_text(cx), "ab\n  de");
 1694        assert_eq!(
 1695            editor.selections.display_ranges(cx),
 1696            &[
 1697                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1698                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1699            ]
 1700        );
 1701    });
 1702
 1703    _ = editor.update(cx, |editor, window, cx| {
 1704        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1705        assert_eq!(editor.display_text(cx), "\n");
 1706        assert_eq!(
 1707            editor.selections.display_ranges(cx),
 1708            &[
 1709                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1710                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1711            ]
 1712        );
 1713    });
 1714}
 1715
 1716#[gpui::test]
 1717fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1718    init_test(cx, |_| {});
 1719    let move_to_beg = MoveToBeginningOfLine {
 1720        stop_at_soft_wraps: false,
 1721        stop_at_indent: false,
 1722    };
 1723
 1724    let move_to_end = MoveToEndOfLine {
 1725        stop_at_soft_wraps: false,
 1726    };
 1727
 1728    let editor = cx.add_window(|window, cx| {
 1729        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1730        build_editor(buffer, window, cx)
 1731    });
 1732
 1733    _ = editor.update(cx, |editor, window, cx| {
 1734        editor.set_wrap_width(Some(140.0.into()), cx);
 1735
 1736        // We expect the following lines after wrapping
 1737        // ```
 1738        // thequickbrownfox
 1739        // jumpedoverthelazydo
 1740        // gs
 1741        // ```
 1742        // The final `gs` was soft-wrapped onto a new line.
 1743        assert_eq!(
 1744            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1745            editor.display_text(cx),
 1746        );
 1747
 1748        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1749        // Start the cursor at the `k` on the first line
 1750        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1751            s.select_display_ranges([
 1752                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1753            ]);
 1754        });
 1755
 1756        // Moving to the beginning of the line should put us at the beginning of the line.
 1757        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1758        assert_eq!(
 1759            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1760            editor.selections.display_ranges(cx)
 1761        );
 1762
 1763        // Moving to the end of the line should put us at the end of the line.
 1764        editor.move_to_end_of_line(&move_to_end, window, cx);
 1765        assert_eq!(
 1766            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1767            editor.selections.display_ranges(cx)
 1768        );
 1769
 1770        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1771        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1772        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1773            s.select_display_ranges([
 1774                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1775            ]);
 1776        });
 1777
 1778        // Moving to the beginning of the line should put us at the start of the second line of
 1779        // display text, i.e., the `j`.
 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 beginning of the line again should be a no-op.
 1787        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1788        assert_eq!(
 1789            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1790            editor.selections.display_ranges(cx)
 1791        );
 1792
 1793        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1794        // next display line.
 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        // Moving to the end of the line again should be a no-op.
 1802        editor.move_to_end_of_line(&move_to_end, window, cx);
 1803        assert_eq!(
 1804            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1805            editor.selections.display_ranges(cx)
 1806        );
 1807    });
 1808}
 1809
 1810#[gpui::test]
 1811fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1812    init_test(cx, |_| {});
 1813
 1814    let move_to_beg = MoveToBeginningOfLine {
 1815        stop_at_soft_wraps: true,
 1816        stop_at_indent: true,
 1817    };
 1818
 1819    let select_to_beg = SelectToBeginningOfLine {
 1820        stop_at_soft_wraps: true,
 1821        stop_at_indent: true,
 1822    };
 1823
 1824    let delete_to_beg = DeleteToBeginningOfLine {
 1825        stop_at_indent: true,
 1826    };
 1827
 1828    let move_to_end = MoveToEndOfLine {
 1829        stop_at_soft_wraps: false,
 1830    };
 1831
 1832    let editor = cx.add_window(|window, cx| {
 1833        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1834        build_editor(buffer, window, cx)
 1835    });
 1836
 1837    _ = editor.update(cx, |editor, window, cx| {
 1838        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1839            s.select_display_ranges([
 1840                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1841                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1842            ]);
 1843        });
 1844
 1845        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1846        // and the second cursor at the first non-whitespace character in the line.
 1847        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1848        assert_eq!(
 1849            editor.selections.display_ranges(cx),
 1850            &[
 1851                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1852                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1853            ]
 1854        );
 1855
 1856        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1857        // and should move the second cursor to the beginning of the line.
 1858        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1859        assert_eq!(
 1860            editor.selections.display_ranges(cx),
 1861            &[
 1862                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1863                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1864            ]
 1865        );
 1866
 1867        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1868        // and should move the second cursor back to the first non-whitespace character in the line.
 1869        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1870        assert_eq!(
 1871            editor.selections.display_ranges(cx),
 1872            &[
 1873                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1874                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1875            ]
 1876        );
 1877
 1878        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1879        // and to the first non-whitespace character in the line for the second cursor.
 1880        editor.move_to_end_of_line(&move_to_end, window, cx);
 1881        editor.move_left(&MoveLeft, window, cx);
 1882        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1883        assert_eq!(
 1884            editor.selections.display_ranges(cx),
 1885            &[
 1886                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1887                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1888            ]
 1889        );
 1890
 1891        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1892        // and should select to the beginning of the line for the second cursor.
 1893        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1894        assert_eq!(
 1895            editor.selections.display_ranges(cx),
 1896            &[
 1897                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1898                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1899            ]
 1900        );
 1901
 1902        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1903        // and should delete to the first non-whitespace character in the line for the second cursor.
 1904        editor.move_to_end_of_line(&move_to_end, window, cx);
 1905        editor.move_left(&MoveLeft, window, cx);
 1906        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1907        assert_eq!(editor.text(cx), "c\n  f");
 1908    });
 1909}
 1910
 1911#[gpui::test]
 1912fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 1913    init_test(cx, |_| {});
 1914
 1915    let move_to_beg = MoveToBeginningOfLine {
 1916        stop_at_soft_wraps: true,
 1917        stop_at_indent: true,
 1918    };
 1919
 1920    let editor = cx.add_window(|window, cx| {
 1921        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 1922        build_editor(buffer, window, cx)
 1923    });
 1924
 1925    _ = editor.update(cx, |editor, window, cx| {
 1926        // test cursor between line_start and indent_start
 1927        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1928            s.select_display_ranges([
 1929                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 1930            ]);
 1931        });
 1932
 1933        // cursor should move to line_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), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1938        );
 1939
 1940        // cursor should move to indent_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), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 1945        );
 1946
 1947        // cursor should move to back to line_start
 1948        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1949        assert_eq!(
 1950            editor.selections.display_ranges(cx),
 1951            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1952        );
 1953    });
 1954}
 1955
 1956#[gpui::test]
 1957fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1958    init_test(cx, |_| {});
 1959
 1960    let editor = cx.add_window(|window, cx| {
 1961        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1962        build_editor(buffer, window, cx)
 1963    });
 1964    _ = editor.update(cx, |editor, window, cx| {
 1965        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1966            s.select_display_ranges([
 1967                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1968                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1969            ])
 1970        });
 1971        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1972        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1973
 1974        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1975        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1976
 1977        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1978        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1979
 1980        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1981        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1982
 1983        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1984        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1985
 1986        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1987        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1988
 1989        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1990        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1991
 1992        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1993        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1994
 1995        editor.move_right(&MoveRight, window, cx);
 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_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2004        assert_selection_ranges(
 2005            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2006            editor,
 2007            cx,
 2008        );
 2009
 2010        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2011        assert_selection_ranges(
 2012            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2013            editor,
 2014            cx,
 2015        );
 2016    });
 2017}
 2018
 2019#[gpui::test]
 2020fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2021    init_test(cx, |_| {});
 2022
 2023    let editor = cx.add_window(|window, cx| {
 2024        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2025        build_editor(buffer, window, cx)
 2026    });
 2027
 2028    _ = editor.update(cx, |editor, window, cx| {
 2029        editor.set_wrap_width(Some(140.0.into()), cx);
 2030        assert_eq!(
 2031            editor.display_text(cx),
 2032            "use one::{\n    two::three::\n    four::five\n};"
 2033        );
 2034
 2035        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2036            s.select_display_ranges([
 2037                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2038            ]);
 2039        });
 2040
 2041        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2042        assert_eq!(
 2043            editor.selections.display_ranges(cx),
 2044            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2045        );
 2046
 2047        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2048        assert_eq!(
 2049            editor.selections.display_ranges(cx),
 2050            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2051        );
 2052
 2053        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2054        assert_eq!(
 2055            editor.selections.display_ranges(cx),
 2056            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2057        );
 2058
 2059        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2060        assert_eq!(
 2061            editor.selections.display_ranges(cx),
 2062            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2063        );
 2064
 2065        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2066        assert_eq!(
 2067            editor.selections.display_ranges(cx),
 2068            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2069        );
 2070
 2071        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2072        assert_eq!(
 2073            editor.selections.display_ranges(cx),
 2074            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2075        );
 2076    });
 2077}
 2078
 2079#[gpui::test]
 2080async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2081    init_test(cx, |_| {});
 2082    let mut cx = EditorTestContext::new(cx).await;
 2083
 2084    let line_height = cx.editor(|editor, window, _| {
 2085        editor
 2086            .style()
 2087            .unwrap()
 2088            .text
 2089            .line_height_in_pixels(window.rem_size())
 2090    });
 2091    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2092
 2093    cx.set_state(
 2094        &r#"ˇone
 2095        two
 2096
 2097        three
 2098        fourˇ
 2099        five
 2100
 2101        six"#
 2102            .unindent(),
 2103    );
 2104
 2105    cx.update_editor(|editor, window, cx| {
 2106        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2107    });
 2108    cx.assert_editor_state(
 2109        &r#"one
 2110        two
 2111        ˇ
 2112        three
 2113        four
 2114        five
 2115        ˇ
 2116        six"#
 2117            .unindent(),
 2118    );
 2119
 2120    cx.update_editor(|editor, window, cx| {
 2121        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2122    });
 2123    cx.assert_editor_state(
 2124        &r#"one
 2125        two
 2126
 2127        three
 2128        four
 2129        five
 2130        ˇ
 2131        sixˇ"#
 2132            .unindent(),
 2133    );
 2134
 2135    cx.update_editor(|editor, window, cx| {
 2136        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2137    });
 2138    cx.assert_editor_state(
 2139        &r#"one
 2140        two
 2141
 2142        three
 2143        four
 2144        five
 2145
 2146        sixˇ"#
 2147            .unindent(),
 2148    );
 2149
 2150    cx.update_editor(|editor, window, cx| {
 2151        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2152    });
 2153    cx.assert_editor_state(
 2154        &r#"one
 2155        two
 2156
 2157        three
 2158        four
 2159        five
 2160        ˇ
 2161        six"#
 2162            .unindent(),
 2163    );
 2164
 2165    cx.update_editor(|editor, window, cx| {
 2166        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2167    });
 2168    cx.assert_editor_state(
 2169        &r#"one
 2170        two
 2171        ˇ
 2172        three
 2173        four
 2174        five
 2175
 2176        six"#
 2177            .unindent(),
 2178    );
 2179
 2180    cx.update_editor(|editor, window, cx| {
 2181        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2182    });
 2183    cx.assert_editor_state(
 2184        &r#"ˇone
 2185        two
 2186
 2187        three
 2188        four
 2189        five
 2190
 2191        six"#
 2192            .unindent(),
 2193    );
 2194}
 2195
 2196#[gpui::test]
 2197async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2198    init_test(cx, |_| {});
 2199    let mut cx = EditorTestContext::new(cx).await;
 2200    let line_height = cx.editor(|editor, window, _| {
 2201        editor
 2202            .style()
 2203            .unwrap()
 2204            .text
 2205            .line_height_in_pixels(window.rem_size())
 2206    });
 2207    let window = cx.window;
 2208    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2209
 2210    cx.set_state(
 2211        r#"ˇone
 2212        two
 2213        three
 2214        four
 2215        five
 2216        six
 2217        seven
 2218        eight
 2219        nine
 2220        ten
 2221        "#,
 2222    );
 2223
 2224    cx.update_editor(|editor, window, cx| {
 2225        assert_eq!(
 2226            editor.snapshot(window, cx).scroll_position(),
 2227            gpui::Point::new(0., 0.)
 2228        );
 2229        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2230        assert_eq!(
 2231            editor.snapshot(window, cx).scroll_position(),
 2232            gpui::Point::new(0., 3.)
 2233        );
 2234        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2235        assert_eq!(
 2236            editor.snapshot(window, cx).scroll_position(),
 2237            gpui::Point::new(0., 6.)
 2238        );
 2239        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2240        assert_eq!(
 2241            editor.snapshot(window, cx).scroll_position(),
 2242            gpui::Point::new(0., 3.)
 2243        );
 2244
 2245        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2246        assert_eq!(
 2247            editor.snapshot(window, cx).scroll_position(),
 2248            gpui::Point::new(0., 1.)
 2249        );
 2250        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2251        assert_eq!(
 2252            editor.snapshot(window, cx).scroll_position(),
 2253            gpui::Point::new(0., 3.)
 2254        );
 2255    });
 2256}
 2257
 2258#[gpui::test]
 2259async fn test_autoscroll(cx: &mut TestAppContext) {
 2260    init_test(cx, |_| {});
 2261    let mut cx = EditorTestContext::new(cx).await;
 2262
 2263    let line_height = cx.update_editor(|editor, window, cx| {
 2264        editor.set_vertical_scroll_margin(2, cx);
 2265        editor
 2266            .style()
 2267            .unwrap()
 2268            .text
 2269            .line_height_in_pixels(window.rem_size())
 2270    });
 2271    let window = cx.window;
 2272    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2273
 2274    cx.set_state(
 2275        r#"ˇone
 2276            two
 2277            three
 2278            four
 2279            five
 2280            six
 2281            seven
 2282            eight
 2283            nine
 2284            ten
 2285        "#,
 2286    );
 2287    cx.update_editor(|editor, window, cx| {
 2288        assert_eq!(
 2289            editor.snapshot(window, cx).scroll_position(),
 2290            gpui::Point::new(0., 0.0)
 2291        );
 2292    });
 2293
 2294    // Add a cursor below the visible area. Since both cursors cannot fit
 2295    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2296    // allows the vertical scroll margin below that cursor.
 2297    cx.update_editor(|editor, window, cx| {
 2298        editor.change_selections(Default::default(), window, cx, |selections| {
 2299            selections.select_ranges([
 2300                Point::new(0, 0)..Point::new(0, 0),
 2301                Point::new(6, 0)..Point::new(6, 0),
 2302            ]);
 2303        })
 2304    });
 2305    cx.update_editor(|editor, window, cx| {
 2306        assert_eq!(
 2307            editor.snapshot(window, cx).scroll_position(),
 2308            gpui::Point::new(0., 3.0)
 2309        );
 2310    });
 2311
 2312    // Move down. The editor cursor scrolls down to track the newest cursor.
 2313    cx.update_editor(|editor, window, cx| {
 2314        editor.move_down(&Default::default(), window, cx);
 2315    });
 2316    cx.update_editor(|editor, window, cx| {
 2317        assert_eq!(
 2318            editor.snapshot(window, cx).scroll_position(),
 2319            gpui::Point::new(0., 4.0)
 2320        );
 2321    });
 2322
 2323    // Add a cursor above the visible area. Since both cursors fit on screen,
 2324    // the editor scrolls to show both.
 2325    cx.update_editor(|editor, window, cx| {
 2326        editor.change_selections(Default::default(), window, cx, |selections| {
 2327            selections.select_ranges([
 2328                Point::new(1, 0)..Point::new(1, 0),
 2329                Point::new(6, 0)..Point::new(6, 0),
 2330            ]);
 2331        })
 2332    });
 2333    cx.update_editor(|editor, window, cx| {
 2334        assert_eq!(
 2335            editor.snapshot(window, cx).scroll_position(),
 2336            gpui::Point::new(0., 1.0)
 2337        );
 2338    });
 2339}
 2340
 2341#[gpui::test]
 2342async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2343    init_test(cx, |_| {});
 2344    let mut cx = EditorTestContext::new(cx).await;
 2345
 2346    let line_height = cx.editor(|editor, window, _cx| {
 2347        editor
 2348            .style()
 2349            .unwrap()
 2350            .text
 2351            .line_height_in_pixels(window.rem_size())
 2352    });
 2353    let window = cx.window;
 2354    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2355    cx.set_state(
 2356        &r#"
 2357        ˇone
 2358        two
 2359        threeˇ
 2360        four
 2361        five
 2362        six
 2363        seven
 2364        eight
 2365        nine
 2366        ten
 2367        "#
 2368        .unindent(),
 2369    );
 2370
 2371    cx.update_editor(|editor, window, cx| {
 2372        editor.move_page_down(&MovePageDown::default(), window, cx)
 2373    });
 2374    cx.assert_editor_state(
 2375        &r#"
 2376        one
 2377        two
 2378        three
 2379        ˇfour
 2380        five
 2381        sixˇ
 2382        seven
 2383        eight
 2384        nine
 2385        ten
 2386        "#
 2387        .unindent(),
 2388    );
 2389
 2390    cx.update_editor(|editor, window, cx| {
 2391        editor.move_page_down(&MovePageDown::default(), window, cx)
 2392    });
 2393    cx.assert_editor_state(
 2394        &r#"
 2395        one
 2396        two
 2397        three
 2398        four
 2399        five
 2400        six
 2401        ˇseven
 2402        eight
 2403        nineˇ
 2404        ten
 2405        "#
 2406        .unindent(),
 2407    );
 2408
 2409    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2410    cx.assert_editor_state(
 2411        &r#"
 2412        one
 2413        two
 2414        three
 2415        ˇfour
 2416        five
 2417        sixˇ
 2418        seven
 2419        eight
 2420        nine
 2421        ten
 2422        "#
 2423        .unindent(),
 2424    );
 2425
 2426    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2427    cx.assert_editor_state(
 2428        &r#"
 2429        ˇone
 2430        two
 2431        threeˇ
 2432        four
 2433        five
 2434        six
 2435        seven
 2436        eight
 2437        nine
 2438        ten
 2439        "#
 2440        .unindent(),
 2441    );
 2442
 2443    // Test select collapsing
 2444    cx.update_editor(|editor, window, cx| {
 2445        editor.move_page_down(&MovePageDown::default(), window, cx);
 2446        editor.move_page_down(&MovePageDown::default(), window, cx);
 2447        editor.move_page_down(&MovePageDown::default(), window, cx);
 2448    });
 2449    cx.assert_editor_state(
 2450        &r#"
 2451        one
 2452        two
 2453        three
 2454        four
 2455        five
 2456        six
 2457        seven
 2458        eight
 2459        nine
 2460        ˇten
 2461        ˇ"#
 2462        .unindent(),
 2463    );
 2464}
 2465
 2466#[gpui::test]
 2467async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2468    init_test(cx, |_| {});
 2469    let mut cx = EditorTestContext::new(cx).await;
 2470    cx.set_state("one «two threeˇ» four");
 2471    cx.update_editor(|editor, window, cx| {
 2472        editor.delete_to_beginning_of_line(
 2473            &DeleteToBeginningOfLine {
 2474                stop_at_indent: false,
 2475            },
 2476            window,
 2477            cx,
 2478        );
 2479        assert_eq!(editor.text(cx), " four");
 2480    });
 2481}
 2482
 2483#[gpui::test]
 2484async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2485    init_test(cx, |_| {});
 2486
 2487    let mut cx = EditorTestContext::new(cx).await;
 2488
 2489    // For an empty selection, the preceding word fragment is deleted.
 2490    // For non-empty selections, only selected characters are deleted.
 2491    cx.set_state("onˇe two t«hreˇ»e four");
 2492    cx.update_editor(|editor, window, cx| {
 2493        editor.delete_to_previous_word_start(
 2494            &DeleteToPreviousWordStart {
 2495                ignore_newlines: false,
 2496                ignore_brackets: false,
 2497            },
 2498            window,
 2499            cx,
 2500        );
 2501    });
 2502    cx.assert_editor_state("ˇe two tˇe four");
 2503
 2504    cx.set_state("e tˇwo te «fˇ»our");
 2505    cx.update_editor(|editor, window, cx| {
 2506        editor.delete_to_next_word_end(
 2507            &DeleteToNextWordEnd {
 2508                ignore_newlines: false,
 2509                ignore_brackets: false,
 2510            },
 2511            window,
 2512            cx,
 2513        );
 2514    });
 2515    cx.assert_editor_state("e tˇ te ˇour");
 2516}
 2517
 2518#[gpui::test]
 2519async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2520    init_test(cx, |_| {});
 2521
 2522    let mut cx = EditorTestContext::new(cx).await;
 2523
 2524    cx.set_state("here is some text    ˇwith a space");
 2525    cx.update_editor(|editor, window, cx| {
 2526        editor.delete_to_previous_word_start(
 2527            &DeleteToPreviousWordStart {
 2528                ignore_newlines: false,
 2529                ignore_brackets: true,
 2530            },
 2531            window,
 2532            cx,
 2533        );
 2534    });
 2535    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2536    cx.assert_editor_state("here is some textˇwith a space");
 2537
 2538    cx.set_state("here is some text    ˇwith a space");
 2539    cx.update_editor(|editor, window, cx| {
 2540        editor.delete_to_previous_word_start(
 2541            &DeleteToPreviousWordStart {
 2542                ignore_newlines: false,
 2543                ignore_brackets: false,
 2544            },
 2545            window,
 2546            cx,
 2547        );
 2548    });
 2549    cx.assert_editor_state("here is some textˇwith a space");
 2550
 2551    cx.set_state("here is some textˇ    with a space");
 2552    cx.update_editor(|editor, window, cx| {
 2553        editor.delete_to_next_word_end(
 2554            &DeleteToNextWordEnd {
 2555                ignore_newlines: false,
 2556                ignore_brackets: true,
 2557            },
 2558            window,
 2559            cx,
 2560        );
 2561    });
 2562    // Same happens in the other direction.
 2563    cx.assert_editor_state("here is some textˇwith a space");
 2564
 2565    cx.set_state("here is some textˇ    with a space");
 2566    cx.update_editor(|editor, window, cx| {
 2567        editor.delete_to_next_word_end(
 2568            &DeleteToNextWordEnd {
 2569                ignore_newlines: false,
 2570                ignore_brackets: false,
 2571            },
 2572            window,
 2573            cx,
 2574        );
 2575    });
 2576    cx.assert_editor_state("here is some textˇwith a space");
 2577
 2578    cx.set_state("here is some textˇ    with a space");
 2579    cx.update_editor(|editor, window, cx| {
 2580        editor.delete_to_next_word_end(
 2581            &DeleteToNextWordEnd {
 2582                ignore_newlines: true,
 2583                ignore_brackets: false,
 2584            },
 2585            window,
 2586            cx,
 2587        );
 2588    });
 2589    cx.assert_editor_state("here is some textˇwith a space");
 2590    cx.update_editor(|editor, window, cx| {
 2591        editor.delete_to_previous_word_start(
 2592            &DeleteToPreviousWordStart {
 2593                ignore_newlines: true,
 2594                ignore_brackets: false,
 2595            },
 2596            window,
 2597            cx,
 2598        );
 2599    });
 2600    cx.assert_editor_state("here is some ˇwith a space");
 2601    cx.update_editor(|editor, window, cx| {
 2602        editor.delete_to_previous_word_start(
 2603            &DeleteToPreviousWordStart {
 2604                ignore_newlines: true,
 2605                ignore_brackets: false,
 2606            },
 2607            window,
 2608            cx,
 2609        );
 2610    });
 2611    // Single whitespaces are removed with the word behind them.
 2612    cx.assert_editor_state("here is ˇwith a space");
 2613    cx.update_editor(|editor, window, cx| {
 2614        editor.delete_to_previous_word_start(
 2615            &DeleteToPreviousWordStart {
 2616                ignore_newlines: true,
 2617                ignore_brackets: false,
 2618            },
 2619            window,
 2620            cx,
 2621        );
 2622    });
 2623    cx.assert_editor_state("here ˇwith a space");
 2624    cx.update_editor(|editor, window, cx| {
 2625        editor.delete_to_previous_word_start(
 2626            &DeleteToPreviousWordStart {
 2627                ignore_newlines: true,
 2628                ignore_brackets: false,
 2629            },
 2630            window,
 2631            cx,
 2632        );
 2633    });
 2634    cx.assert_editor_state("ˇwith a space");
 2635    cx.update_editor(|editor, window, cx| {
 2636        editor.delete_to_previous_word_start(
 2637            &DeleteToPreviousWordStart {
 2638                ignore_newlines: true,
 2639                ignore_brackets: false,
 2640            },
 2641            window,
 2642            cx,
 2643        );
 2644    });
 2645    cx.assert_editor_state("ˇwith a space");
 2646    cx.update_editor(|editor, window, cx| {
 2647        editor.delete_to_next_word_end(
 2648            &DeleteToNextWordEnd {
 2649                ignore_newlines: true,
 2650                ignore_brackets: false,
 2651            },
 2652            window,
 2653            cx,
 2654        );
 2655    });
 2656    // Same happens in the other direction.
 2657    cx.assert_editor_state("ˇ a space");
 2658    cx.update_editor(|editor, window, cx| {
 2659        editor.delete_to_next_word_end(
 2660            &DeleteToNextWordEnd {
 2661                ignore_newlines: true,
 2662                ignore_brackets: false,
 2663            },
 2664            window,
 2665            cx,
 2666        );
 2667    });
 2668    cx.assert_editor_state("ˇ space");
 2669    cx.update_editor(|editor, window, cx| {
 2670        editor.delete_to_next_word_end(
 2671            &DeleteToNextWordEnd {
 2672                ignore_newlines: true,
 2673                ignore_brackets: false,
 2674            },
 2675            window,
 2676            cx,
 2677        );
 2678    });
 2679    cx.assert_editor_state("ˇ");
 2680    cx.update_editor(|editor, window, cx| {
 2681        editor.delete_to_next_word_end(
 2682            &DeleteToNextWordEnd {
 2683                ignore_newlines: true,
 2684                ignore_brackets: false,
 2685            },
 2686            window,
 2687            cx,
 2688        );
 2689    });
 2690    cx.assert_editor_state("ˇ");
 2691    cx.update_editor(|editor, window, cx| {
 2692        editor.delete_to_previous_word_start(
 2693            &DeleteToPreviousWordStart {
 2694                ignore_newlines: true,
 2695                ignore_brackets: false,
 2696            },
 2697            window,
 2698            cx,
 2699        );
 2700    });
 2701    cx.assert_editor_state("ˇ");
 2702}
 2703
 2704#[gpui::test]
 2705async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2706    init_test(cx, |_| {});
 2707
 2708    let language = Arc::new(
 2709        Language::new(
 2710            LanguageConfig {
 2711                brackets: BracketPairConfig {
 2712                    pairs: vec![
 2713                        BracketPair {
 2714                            start: "\"".to_string(),
 2715                            end: "\"".to_string(),
 2716                            close: true,
 2717                            surround: true,
 2718                            newline: false,
 2719                        },
 2720                        BracketPair {
 2721                            start: "(".to_string(),
 2722                            end: ")".to_string(),
 2723                            close: true,
 2724                            surround: true,
 2725                            newline: true,
 2726                        },
 2727                    ],
 2728                    ..BracketPairConfig::default()
 2729                },
 2730                ..LanguageConfig::default()
 2731            },
 2732            Some(tree_sitter_rust::LANGUAGE.into()),
 2733        )
 2734        .with_brackets_query(
 2735            r#"
 2736                ("(" @open ")" @close)
 2737                ("\"" @open "\"" @close)
 2738            "#,
 2739        )
 2740        .unwrap(),
 2741    );
 2742
 2743    let mut cx = EditorTestContext::new(cx).await;
 2744    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2745
 2746    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2747    cx.update_editor(|editor, window, cx| {
 2748        editor.delete_to_previous_word_start(
 2749            &DeleteToPreviousWordStart {
 2750                ignore_newlines: true,
 2751                ignore_brackets: false,
 2752            },
 2753            window,
 2754            cx,
 2755        );
 2756    });
 2757    // Deletion stops before brackets if asked to not ignore them.
 2758    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2759    cx.update_editor(|editor, window, cx| {
 2760        editor.delete_to_previous_word_start(
 2761            &DeleteToPreviousWordStart {
 2762                ignore_newlines: true,
 2763                ignore_brackets: false,
 2764            },
 2765            window,
 2766            cx,
 2767        );
 2768    });
 2769    // Deletion has to remove a single bracket and then stop again.
 2770    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2771
 2772    cx.update_editor(|editor, window, cx| {
 2773        editor.delete_to_previous_word_start(
 2774            &DeleteToPreviousWordStart {
 2775                ignore_newlines: true,
 2776                ignore_brackets: false,
 2777            },
 2778            window,
 2779            cx,
 2780        );
 2781    });
 2782    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2783
 2784    cx.update_editor(|editor, window, cx| {
 2785        editor.delete_to_previous_word_start(
 2786            &DeleteToPreviousWordStart {
 2787                ignore_newlines: true,
 2788                ignore_brackets: false,
 2789            },
 2790            window,
 2791            cx,
 2792        );
 2793    });
 2794    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2795
 2796    cx.update_editor(|editor, window, cx| {
 2797        editor.delete_to_previous_word_start(
 2798            &DeleteToPreviousWordStart {
 2799                ignore_newlines: true,
 2800                ignore_brackets: false,
 2801            },
 2802            window,
 2803            cx,
 2804        );
 2805    });
 2806    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2807
 2808    cx.update_editor(|editor, window, cx| {
 2809        editor.delete_to_next_word_end(
 2810            &DeleteToNextWordEnd {
 2811                ignore_newlines: true,
 2812                ignore_brackets: false,
 2813            },
 2814            window,
 2815            cx,
 2816        );
 2817    });
 2818    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2819    cx.assert_editor_state(r#"ˇ");"#);
 2820
 2821    cx.update_editor(|editor, window, cx| {
 2822        editor.delete_to_next_word_end(
 2823            &DeleteToNextWordEnd {
 2824                ignore_newlines: true,
 2825                ignore_brackets: false,
 2826            },
 2827            window,
 2828            cx,
 2829        );
 2830    });
 2831    cx.assert_editor_state(r#"ˇ"#);
 2832
 2833    cx.update_editor(|editor, window, cx| {
 2834        editor.delete_to_next_word_end(
 2835            &DeleteToNextWordEnd {
 2836                ignore_newlines: true,
 2837                ignore_brackets: false,
 2838            },
 2839            window,
 2840            cx,
 2841        );
 2842    });
 2843    cx.assert_editor_state(r#"ˇ"#);
 2844
 2845    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2846    cx.update_editor(|editor, window, cx| {
 2847        editor.delete_to_previous_word_start(
 2848            &DeleteToPreviousWordStart {
 2849                ignore_newlines: true,
 2850                ignore_brackets: true,
 2851            },
 2852            window,
 2853            cx,
 2854        );
 2855    });
 2856    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2857}
 2858
 2859#[gpui::test]
 2860fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2861    init_test(cx, |_| {});
 2862
 2863    let editor = cx.add_window(|window, cx| {
 2864        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2865        build_editor(buffer, window, cx)
 2866    });
 2867    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2868        ignore_newlines: false,
 2869        ignore_brackets: false,
 2870    };
 2871    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2872        ignore_newlines: true,
 2873        ignore_brackets: false,
 2874    };
 2875
 2876    _ = editor.update(cx, |editor, window, cx| {
 2877        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2878            s.select_display_ranges([
 2879                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2880            ])
 2881        });
 2882        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2883        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2884        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2885        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2886        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2887        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2888        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2889        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2890        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2891        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2892        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2893        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2894    });
 2895}
 2896
 2897#[gpui::test]
 2898fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2899    init_test(cx, |_| {});
 2900
 2901    let editor = cx.add_window(|window, cx| {
 2902        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2903        build_editor(buffer, window, cx)
 2904    });
 2905    let del_to_next_word_end = DeleteToNextWordEnd {
 2906        ignore_newlines: false,
 2907        ignore_brackets: false,
 2908    };
 2909    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2910        ignore_newlines: true,
 2911        ignore_brackets: false,
 2912    };
 2913
 2914    _ = editor.update(cx, |editor, window, cx| {
 2915        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2916            s.select_display_ranges([
 2917                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2918            ])
 2919        });
 2920        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2921        assert_eq!(
 2922            editor.buffer.read(cx).read(cx).text(),
 2923            "one\n   two\nthree\n   four"
 2924        );
 2925        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2926        assert_eq!(
 2927            editor.buffer.read(cx).read(cx).text(),
 2928            "\n   two\nthree\n   four"
 2929        );
 2930        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2931        assert_eq!(
 2932            editor.buffer.read(cx).read(cx).text(),
 2933            "two\nthree\n   four"
 2934        );
 2935        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2936        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2937        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2938        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2939        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2940        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 2941        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2942        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2943    });
 2944}
 2945
 2946#[gpui::test]
 2947fn test_newline(cx: &mut TestAppContext) {
 2948    init_test(cx, |_| {});
 2949
 2950    let editor = cx.add_window(|window, cx| {
 2951        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2952        build_editor(buffer, window, cx)
 2953    });
 2954
 2955    _ = editor.update(cx, |editor, window, cx| {
 2956        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2957            s.select_display_ranges([
 2958                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2959                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2960                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2961            ])
 2962        });
 2963
 2964        editor.newline(&Newline, window, cx);
 2965        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2966    });
 2967}
 2968
 2969#[gpui::test]
 2970fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2971    init_test(cx, |_| {});
 2972
 2973    let editor = cx.add_window(|window, cx| {
 2974        let buffer = MultiBuffer::build_simple(
 2975            "
 2976                a
 2977                b(
 2978                    X
 2979                )
 2980                c(
 2981                    X
 2982                )
 2983            "
 2984            .unindent()
 2985            .as_str(),
 2986            cx,
 2987        );
 2988        let mut editor = build_editor(buffer, window, cx);
 2989        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2990            s.select_ranges([
 2991                Point::new(2, 4)..Point::new(2, 5),
 2992                Point::new(5, 4)..Point::new(5, 5),
 2993            ])
 2994        });
 2995        editor
 2996    });
 2997
 2998    _ = editor.update(cx, |editor, window, cx| {
 2999        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3000        editor.buffer.update(cx, |buffer, cx| {
 3001            buffer.edit(
 3002                [
 3003                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3004                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3005                ],
 3006                None,
 3007                cx,
 3008            );
 3009            assert_eq!(
 3010                buffer.read(cx).text(),
 3011                "
 3012                    a
 3013                    b()
 3014                    c()
 3015                "
 3016                .unindent()
 3017            );
 3018        });
 3019        assert_eq!(
 3020            editor.selections.ranges(cx),
 3021            &[
 3022                Point::new(1, 2)..Point::new(1, 2),
 3023                Point::new(2, 2)..Point::new(2, 2),
 3024            ],
 3025        );
 3026
 3027        editor.newline(&Newline, window, cx);
 3028        assert_eq!(
 3029            editor.text(cx),
 3030            "
 3031                a
 3032                b(
 3033                )
 3034                c(
 3035                )
 3036            "
 3037            .unindent()
 3038        );
 3039
 3040        // The selections are moved after the inserted newlines
 3041        assert_eq!(
 3042            editor.selections.ranges(cx),
 3043            &[
 3044                Point::new(2, 0)..Point::new(2, 0),
 3045                Point::new(4, 0)..Point::new(4, 0),
 3046            ],
 3047        );
 3048    });
 3049}
 3050
 3051#[gpui::test]
 3052async fn test_newline_above(cx: &mut TestAppContext) {
 3053    init_test(cx, |settings| {
 3054        settings.defaults.tab_size = NonZeroU32::new(4)
 3055    });
 3056
 3057    let language = Arc::new(
 3058        Language::new(
 3059            LanguageConfig::default(),
 3060            Some(tree_sitter_rust::LANGUAGE.into()),
 3061        )
 3062        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3063        .unwrap(),
 3064    );
 3065
 3066    let mut cx = EditorTestContext::new(cx).await;
 3067    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3068    cx.set_state(indoc! {"
 3069        const a: ˇA = (
 3070 3071                «const_functionˇ»(ˇ),
 3072                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3073 3074        ˇ);ˇ
 3075    "});
 3076
 3077    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3078    cx.assert_editor_state(indoc! {"
 3079        ˇ
 3080        const a: A = (
 3081            ˇ
 3082            (
 3083                ˇ
 3084                ˇ
 3085                const_function(),
 3086                ˇ
 3087                ˇ
 3088                ˇ
 3089                ˇ
 3090                something_else,
 3091                ˇ
 3092            )
 3093            ˇ
 3094            ˇ
 3095        );
 3096    "});
 3097}
 3098
 3099#[gpui::test]
 3100async fn test_newline_below(cx: &mut TestAppContext) {
 3101    init_test(cx, |settings| {
 3102        settings.defaults.tab_size = NonZeroU32::new(4)
 3103    });
 3104
 3105    let language = Arc::new(
 3106        Language::new(
 3107            LanguageConfig::default(),
 3108            Some(tree_sitter_rust::LANGUAGE.into()),
 3109        )
 3110        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3111        .unwrap(),
 3112    );
 3113
 3114    let mut cx = EditorTestContext::new(cx).await;
 3115    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3116    cx.set_state(indoc! {"
 3117        const a: ˇA = (
 3118 3119                «const_functionˇ»(ˇ),
 3120                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3121 3122        ˇ);ˇ
 3123    "});
 3124
 3125    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3126    cx.assert_editor_state(indoc! {"
 3127        const a: A = (
 3128            ˇ
 3129            (
 3130                ˇ
 3131                const_function(),
 3132                ˇ
 3133                ˇ
 3134                something_else,
 3135                ˇ
 3136                ˇ
 3137                ˇ
 3138                ˇ
 3139            )
 3140            ˇ
 3141        );
 3142        ˇ
 3143        ˇ
 3144    "});
 3145}
 3146
 3147#[gpui::test]
 3148async fn test_newline_comments(cx: &mut TestAppContext) {
 3149    init_test(cx, |settings| {
 3150        settings.defaults.tab_size = NonZeroU32::new(4)
 3151    });
 3152
 3153    let language = Arc::new(Language::new(
 3154        LanguageConfig {
 3155            line_comments: vec!["// ".into()],
 3156            ..LanguageConfig::default()
 3157        },
 3158        None,
 3159    ));
 3160    {
 3161        let mut cx = EditorTestContext::new(cx).await;
 3162        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3163        cx.set_state(indoc! {"
 3164        // Fooˇ
 3165    "});
 3166
 3167        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3168        cx.assert_editor_state(indoc! {"
 3169        // Foo
 3170        // ˇ
 3171    "});
 3172        // Ensure that we add comment prefix when existing line contains space
 3173        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3174        cx.assert_editor_state(
 3175            indoc! {"
 3176        // Foo
 3177        //s
 3178        // ˇ
 3179    "}
 3180            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3181            .as_str(),
 3182        );
 3183        // Ensure that we add comment prefix when existing line does not contain space
 3184        cx.set_state(indoc! {"
 3185        // Foo
 3186        //ˇ
 3187    "});
 3188        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3189        cx.assert_editor_state(indoc! {"
 3190        // Foo
 3191        //
 3192        // ˇ
 3193    "});
 3194        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3195        cx.set_state(indoc! {"
 3196        ˇ// Foo
 3197    "});
 3198        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3199        cx.assert_editor_state(indoc! {"
 3200
 3201        ˇ// Foo
 3202    "});
 3203    }
 3204    // Ensure that comment continuations can be disabled.
 3205    update_test_language_settings(cx, |settings| {
 3206        settings.defaults.extend_comment_on_newline = Some(false);
 3207    });
 3208    let mut cx = EditorTestContext::new(cx).await;
 3209    cx.set_state(indoc! {"
 3210        // Fooˇ
 3211    "});
 3212    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3213    cx.assert_editor_state(indoc! {"
 3214        // Foo
 3215        ˇ
 3216    "});
 3217}
 3218
 3219#[gpui::test]
 3220async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3221    init_test(cx, |settings| {
 3222        settings.defaults.tab_size = NonZeroU32::new(4)
 3223    });
 3224
 3225    let language = Arc::new(Language::new(
 3226        LanguageConfig {
 3227            line_comments: vec!["// ".into(), "/// ".into()],
 3228            ..LanguageConfig::default()
 3229        },
 3230        None,
 3231    ));
 3232    {
 3233        let mut cx = EditorTestContext::new(cx).await;
 3234        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3235        cx.set_state(indoc! {"
 3236        //ˇ
 3237    "});
 3238        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3239        cx.assert_editor_state(indoc! {"
 3240        //
 3241        // ˇ
 3242    "});
 3243
 3244        cx.set_state(indoc! {"
 3245        ///ˇ
 3246    "});
 3247        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3248        cx.assert_editor_state(indoc! {"
 3249        ///
 3250        /// ˇ
 3251    "});
 3252    }
 3253}
 3254
 3255#[gpui::test]
 3256async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3257    init_test(cx, |settings| {
 3258        settings.defaults.tab_size = NonZeroU32::new(4)
 3259    });
 3260
 3261    let language = Arc::new(
 3262        Language::new(
 3263            LanguageConfig {
 3264                documentation_comment: Some(language::BlockCommentConfig {
 3265                    start: "/**".into(),
 3266                    end: "*/".into(),
 3267                    prefix: "* ".into(),
 3268                    tab_size: 1,
 3269                }),
 3270
 3271                ..LanguageConfig::default()
 3272            },
 3273            Some(tree_sitter_rust::LANGUAGE.into()),
 3274        )
 3275        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3276        .unwrap(),
 3277    );
 3278
 3279    {
 3280        let mut cx = EditorTestContext::new(cx).await;
 3281        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3282        cx.set_state(indoc! {"
 3283        /**ˇ
 3284    "});
 3285
 3286        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3287        cx.assert_editor_state(indoc! {"
 3288        /**
 3289         * ˇ
 3290    "});
 3291        // Ensure that if cursor is before the comment start,
 3292        // we do not actually insert a comment prefix.
 3293        cx.set_state(indoc! {"
 3294        ˇ/**
 3295    "});
 3296        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3297        cx.assert_editor_state(indoc! {"
 3298
 3299        ˇ/**
 3300    "});
 3301        // Ensure that if cursor is between it doesn't add comment prefix.
 3302        cx.set_state(indoc! {"
 3303        /*ˇ*
 3304    "});
 3305        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3306        cx.assert_editor_state(indoc! {"
 3307        /*
 3308        ˇ*
 3309    "});
 3310        // Ensure that if suffix exists on same line after cursor it adds new line.
 3311        cx.set_state(indoc! {"
 3312        /**ˇ*/
 3313    "});
 3314        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3315        cx.assert_editor_state(indoc! {"
 3316        /**
 3317         * ˇ
 3318         */
 3319    "});
 3320        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3321        cx.set_state(indoc! {"
 3322        /**ˇ */
 3323    "});
 3324        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3325        cx.assert_editor_state(indoc! {"
 3326        /**
 3327         * ˇ
 3328         */
 3329    "});
 3330        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3331        cx.set_state(indoc! {"
 3332        /** ˇ*/
 3333    "});
 3334        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3335        cx.assert_editor_state(
 3336            indoc! {"
 3337        /**s
 3338         * ˇ
 3339         */
 3340    "}
 3341            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3342            .as_str(),
 3343        );
 3344        // Ensure that delimiter space is preserved when newline on already
 3345        // spaced delimiter.
 3346        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3347        cx.assert_editor_state(
 3348            indoc! {"
 3349        /**s
 3350         *s
 3351         * ˇ
 3352         */
 3353    "}
 3354            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3355            .as_str(),
 3356        );
 3357        // Ensure that delimiter space is preserved when space is not
 3358        // on existing delimiter.
 3359        cx.set_state(indoc! {"
 3360        /**
 3361 3362         */
 3363    "});
 3364        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3365        cx.assert_editor_state(indoc! {"
 3366        /**
 3367         *
 3368         * ˇ
 3369         */
 3370    "});
 3371        // Ensure that if suffix exists on same line after cursor it
 3372        // doesn't add extra new line if prefix is not on same line.
 3373        cx.set_state(indoc! {"
 3374        /**
 3375        ˇ*/
 3376    "});
 3377        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3378        cx.assert_editor_state(indoc! {"
 3379        /**
 3380
 3381        ˇ*/
 3382    "});
 3383        // Ensure that it detects suffix after existing prefix.
 3384        cx.set_state(indoc! {"
 3385        /**ˇ/
 3386    "});
 3387        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3388        cx.assert_editor_state(indoc! {"
 3389        /**
 3390        ˇ/
 3391    "});
 3392        // Ensure that if suffix exists on same line before
 3393        // cursor it does not add comment prefix.
 3394        cx.set_state(indoc! {"
 3395        /** */ˇ
 3396    "});
 3397        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3398        cx.assert_editor_state(indoc! {"
 3399        /** */
 3400        ˇ
 3401    "});
 3402        // Ensure that if suffix exists on same line before
 3403        // cursor it does not add comment prefix.
 3404        cx.set_state(indoc! {"
 3405        /**
 3406         *
 3407         */ˇ
 3408    "});
 3409        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3410        cx.assert_editor_state(indoc! {"
 3411        /**
 3412         *
 3413         */
 3414         ˇ
 3415    "});
 3416
 3417        // Ensure that inline comment followed by code
 3418        // doesn't add comment prefix on newline
 3419        cx.set_state(indoc! {"
 3420        /** */ textˇ
 3421    "});
 3422        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3423        cx.assert_editor_state(indoc! {"
 3424        /** */ text
 3425        ˇ
 3426    "});
 3427
 3428        // Ensure that text after comment end tag
 3429        // doesn't add comment prefix on newline
 3430        cx.set_state(indoc! {"
 3431        /**
 3432         *
 3433         */ˇtext
 3434    "});
 3435        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3436        cx.assert_editor_state(indoc! {"
 3437        /**
 3438         *
 3439         */
 3440         ˇtext
 3441    "});
 3442
 3443        // Ensure if not comment block it doesn't
 3444        // add comment prefix on newline
 3445        cx.set_state(indoc! {"
 3446        * textˇ
 3447    "});
 3448        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3449        cx.assert_editor_state(indoc! {"
 3450        * text
 3451        ˇ
 3452    "});
 3453    }
 3454    // Ensure that comment continuations can be disabled.
 3455    update_test_language_settings(cx, |settings| {
 3456        settings.defaults.extend_comment_on_newline = Some(false);
 3457    });
 3458    let mut cx = EditorTestContext::new(cx).await;
 3459    cx.set_state(indoc! {"
 3460        /**ˇ
 3461    "});
 3462    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3463    cx.assert_editor_state(indoc! {"
 3464        /**
 3465        ˇ
 3466    "});
 3467}
 3468
 3469#[gpui::test]
 3470async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3471    init_test(cx, |settings| {
 3472        settings.defaults.tab_size = NonZeroU32::new(4)
 3473    });
 3474
 3475    let lua_language = Arc::new(Language::new(
 3476        LanguageConfig {
 3477            line_comments: vec!["--".into()],
 3478            block_comment: Some(language::BlockCommentConfig {
 3479                start: "--[[".into(),
 3480                prefix: "".into(),
 3481                end: "]]".into(),
 3482                tab_size: 0,
 3483            }),
 3484            ..LanguageConfig::default()
 3485        },
 3486        None,
 3487    ));
 3488
 3489    let mut cx = EditorTestContext::new(cx).await;
 3490    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3491
 3492    // Line with line comment should extend
 3493    cx.set_state(indoc! {"
 3494        --ˇ
 3495    "});
 3496    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3497    cx.assert_editor_state(indoc! {"
 3498        --
 3499        --ˇ
 3500    "});
 3501
 3502    // Line with block comment that matches line comment should not extend
 3503    cx.set_state(indoc! {"
 3504        --[[ˇ
 3505    "});
 3506    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3507    cx.assert_editor_state(indoc! {"
 3508        --[[
 3509        ˇ
 3510    "});
 3511}
 3512
 3513#[gpui::test]
 3514fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3515    init_test(cx, |_| {});
 3516
 3517    let editor = cx.add_window(|window, cx| {
 3518        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3519        let mut editor = build_editor(buffer, window, cx);
 3520        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3521            s.select_ranges([3..4, 11..12, 19..20])
 3522        });
 3523        editor
 3524    });
 3525
 3526    _ = editor.update(cx, |editor, window, cx| {
 3527        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3528        editor.buffer.update(cx, |buffer, cx| {
 3529            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3530            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3531        });
 3532        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3533
 3534        editor.insert("Z", window, cx);
 3535        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3536
 3537        // The selections are moved after the inserted characters
 3538        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3539    });
 3540}
 3541
 3542#[gpui::test]
 3543async fn test_tab(cx: &mut TestAppContext) {
 3544    init_test(cx, |settings| {
 3545        settings.defaults.tab_size = NonZeroU32::new(3)
 3546    });
 3547
 3548    let mut cx = EditorTestContext::new(cx).await;
 3549    cx.set_state(indoc! {"
 3550        ˇabˇc
 3551        ˇ🏀ˇ🏀ˇefg
 3552 3553    "});
 3554    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3555    cx.assert_editor_state(indoc! {"
 3556           ˇab ˇc
 3557           ˇ🏀  ˇ🏀  ˇefg
 3558        d  ˇ
 3559    "});
 3560
 3561    cx.set_state(indoc! {"
 3562        a
 3563        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3564    "});
 3565    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3566    cx.assert_editor_state(indoc! {"
 3567        a
 3568           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3569    "});
 3570}
 3571
 3572#[gpui::test]
 3573async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3574    init_test(cx, |_| {});
 3575
 3576    let mut cx = EditorTestContext::new(cx).await;
 3577    let language = Arc::new(
 3578        Language::new(
 3579            LanguageConfig::default(),
 3580            Some(tree_sitter_rust::LANGUAGE.into()),
 3581        )
 3582        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3583        .unwrap(),
 3584    );
 3585    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3586
 3587    // test when all cursors are not at suggested indent
 3588    // then simply move to their suggested indent location
 3589    cx.set_state(indoc! {"
 3590        const a: B = (
 3591            c(
 3592        ˇ
 3593        ˇ    )
 3594        );
 3595    "});
 3596    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3597    cx.assert_editor_state(indoc! {"
 3598        const a: B = (
 3599            c(
 3600                ˇ
 3601            ˇ)
 3602        );
 3603    "});
 3604
 3605    // test cursor already at suggested indent not moving when
 3606    // other cursors are yet to reach their suggested indents
 3607    cx.set_state(indoc! {"
 3608        ˇ
 3609        const a: B = (
 3610            c(
 3611                d(
 3612        ˇ
 3613                )
 3614        ˇ
 3615        ˇ    )
 3616        );
 3617    "});
 3618    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3619    cx.assert_editor_state(indoc! {"
 3620        ˇ
 3621        const a: B = (
 3622            c(
 3623                d(
 3624                    ˇ
 3625                )
 3626                ˇ
 3627            ˇ)
 3628        );
 3629    "});
 3630    // test when all cursors are at suggested indent then tab is inserted
 3631    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3632    cx.assert_editor_state(indoc! {"
 3633            ˇ
 3634        const a: B = (
 3635            c(
 3636                d(
 3637                        ˇ
 3638                )
 3639                    ˇ
 3640                ˇ)
 3641        );
 3642    "});
 3643
 3644    // test when current indent is less than suggested indent,
 3645    // we adjust line to match suggested indent and move cursor to it
 3646    //
 3647    // when no other cursor is at word boundary, all of them should move
 3648    cx.set_state(indoc! {"
 3649        const a: B = (
 3650            c(
 3651                d(
 3652        ˇ
 3653        ˇ   )
 3654        ˇ   )
 3655        );
 3656    "});
 3657    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3658    cx.assert_editor_state(indoc! {"
 3659        const a: B = (
 3660            c(
 3661                d(
 3662                    ˇ
 3663                ˇ)
 3664            ˇ)
 3665        );
 3666    "});
 3667
 3668    // test when current indent is less than suggested indent,
 3669    // we adjust line to match suggested indent and move cursor to it
 3670    //
 3671    // when some other cursor is at word boundary, it should not move
 3672    cx.set_state(indoc! {"
 3673        const a: B = (
 3674            c(
 3675                d(
 3676        ˇ
 3677        ˇ   )
 3678           ˇ)
 3679        );
 3680    "});
 3681    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3682    cx.assert_editor_state(indoc! {"
 3683        const a: B = (
 3684            c(
 3685                d(
 3686                    ˇ
 3687                ˇ)
 3688            ˇ)
 3689        );
 3690    "});
 3691
 3692    // test when current indent is more than suggested indent,
 3693    // we just move cursor to current indent instead of suggested indent
 3694    //
 3695    // when no other cursor is at word boundary, all of them should move
 3696    cx.set_state(indoc! {"
 3697        const a: B = (
 3698            c(
 3699                d(
 3700        ˇ
 3701        ˇ                )
 3702        ˇ   )
 3703        );
 3704    "});
 3705    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3706    cx.assert_editor_state(indoc! {"
 3707        const a: B = (
 3708            c(
 3709                d(
 3710                    ˇ
 3711                        ˇ)
 3712            ˇ)
 3713        );
 3714    "});
 3715    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3716    cx.assert_editor_state(indoc! {"
 3717        const a: B = (
 3718            c(
 3719                d(
 3720                        ˇ
 3721                            ˇ)
 3722                ˇ)
 3723        );
 3724    "});
 3725
 3726    // test when current indent is more than suggested indent,
 3727    // we just move cursor to current indent instead of suggested indent
 3728    //
 3729    // when some other cursor is at word boundary, it doesn't move
 3730    cx.set_state(indoc! {"
 3731        const a: B = (
 3732            c(
 3733                d(
 3734        ˇ
 3735        ˇ                )
 3736            ˇ)
 3737        );
 3738    "});
 3739    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3740    cx.assert_editor_state(indoc! {"
 3741        const a: B = (
 3742            c(
 3743                d(
 3744                    ˇ
 3745                        ˇ)
 3746            ˇ)
 3747        );
 3748    "});
 3749
 3750    // handle auto-indent when there are multiple cursors on the same line
 3751    cx.set_state(indoc! {"
 3752        const a: B = (
 3753            c(
 3754        ˇ    ˇ
 3755        ˇ    )
 3756        );
 3757    "});
 3758    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3759    cx.assert_editor_state(indoc! {"
 3760        const a: B = (
 3761            c(
 3762                ˇ
 3763            ˇ)
 3764        );
 3765    "});
 3766}
 3767
 3768#[gpui::test]
 3769async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3770    init_test(cx, |settings| {
 3771        settings.defaults.tab_size = NonZeroU32::new(3)
 3772    });
 3773
 3774    let mut cx = EditorTestContext::new(cx).await;
 3775    cx.set_state(indoc! {"
 3776         ˇ
 3777        \t ˇ
 3778        \t  ˇ
 3779        \t   ˇ
 3780         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3781    "});
 3782
 3783    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3784    cx.assert_editor_state(indoc! {"
 3785           ˇ
 3786        \t   ˇ
 3787        \t   ˇ
 3788        \t      ˇ
 3789         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3790    "});
 3791}
 3792
 3793#[gpui::test]
 3794async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3795    init_test(cx, |settings| {
 3796        settings.defaults.tab_size = NonZeroU32::new(4)
 3797    });
 3798
 3799    let language = Arc::new(
 3800        Language::new(
 3801            LanguageConfig::default(),
 3802            Some(tree_sitter_rust::LANGUAGE.into()),
 3803        )
 3804        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3805        .unwrap(),
 3806    );
 3807
 3808    let mut cx = EditorTestContext::new(cx).await;
 3809    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3810    cx.set_state(indoc! {"
 3811        fn a() {
 3812            if b {
 3813        \t ˇc
 3814            }
 3815        }
 3816    "});
 3817
 3818    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3819    cx.assert_editor_state(indoc! {"
 3820        fn a() {
 3821            if b {
 3822                ˇc
 3823            }
 3824        }
 3825    "});
 3826}
 3827
 3828#[gpui::test]
 3829async fn test_indent_outdent(cx: &mut TestAppContext) {
 3830    init_test(cx, |settings| {
 3831        settings.defaults.tab_size = NonZeroU32::new(4);
 3832    });
 3833
 3834    let mut cx = EditorTestContext::new(cx).await;
 3835
 3836    cx.set_state(indoc! {"
 3837          «oneˇ» «twoˇ»
 3838        three
 3839         four
 3840    "});
 3841    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3842    cx.assert_editor_state(indoc! {"
 3843            «oneˇ» «twoˇ»
 3844        three
 3845         four
 3846    "});
 3847
 3848    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3849    cx.assert_editor_state(indoc! {"
 3850        «oneˇ» «twoˇ»
 3851        three
 3852         four
 3853    "});
 3854
 3855    // select across line ending
 3856    cx.set_state(indoc! {"
 3857        one two
 3858        t«hree
 3859        ˇ» four
 3860    "});
 3861    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3862    cx.assert_editor_state(indoc! {"
 3863        one two
 3864            t«hree
 3865        ˇ» four
 3866    "});
 3867
 3868    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3869    cx.assert_editor_state(indoc! {"
 3870        one two
 3871        t«hree
 3872        ˇ» four
 3873    "});
 3874
 3875    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3876    cx.set_state(indoc! {"
 3877        one two
 3878        ˇthree
 3879            four
 3880    "});
 3881    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3882    cx.assert_editor_state(indoc! {"
 3883        one two
 3884            ˇthree
 3885            four
 3886    "});
 3887
 3888    cx.set_state(indoc! {"
 3889        one two
 3890        ˇ    three
 3891            four
 3892    "});
 3893    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3894    cx.assert_editor_state(indoc! {"
 3895        one two
 3896        ˇthree
 3897            four
 3898    "});
 3899}
 3900
 3901#[gpui::test]
 3902async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3903    // This is a regression test for issue #33761
 3904    init_test(cx, |_| {});
 3905
 3906    let mut cx = EditorTestContext::new(cx).await;
 3907    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3908    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3909
 3910    cx.set_state(
 3911        r#"ˇ#     ingress:
 3912ˇ#         api:
 3913ˇ#             enabled: false
 3914ˇ#             pathType: Prefix
 3915ˇ#           console:
 3916ˇ#               enabled: false
 3917ˇ#               pathType: Prefix
 3918"#,
 3919    );
 3920
 3921    // Press tab to indent all lines
 3922    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3923
 3924    cx.assert_editor_state(
 3925        r#"    ˇ#     ingress:
 3926    ˇ#         api:
 3927    ˇ#             enabled: false
 3928    ˇ#             pathType: Prefix
 3929    ˇ#           console:
 3930    ˇ#               enabled: false
 3931    ˇ#               pathType: Prefix
 3932"#,
 3933    );
 3934}
 3935
 3936#[gpui::test]
 3937async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3938    // This is a test to make sure our fix for issue #33761 didn't break anything
 3939    init_test(cx, |_| {});
 3940
 3941    let mut cx = EditorTestContext::new(cx).await;
 3942    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3943    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3944
 3945    cx.set_state(
 3946        r#"ˇingress:
 3947ˇ  api:
 3948ˇ    enabled: false
 3949ˇ    pathType: Prefix
 3950"#,
 3951    );
 3952
 3953    // Press tab to indent all lines
 3954    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3955
 3956    cx.assert_editor_state(
 3957        r#"ˇingress:
 3958    ˇapi:
 3959        ˇenabled: false
 3960        ˇpathType: Prefix
 3961"#,
 3962    );
 3963}
 3964
 3965#[gpui::test]
 3966async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3967    init_test(cx, |settings| {
 3968        settings.defaults.hard_tabs = Some(true);
 3969    });
 3970
 3971    let mut cx = EditorTestContext::new(cx).await;
 3972
 3973    // select two ranges on one line
 3974    cx.set_state(indoc! {"
 3975        «oneˇ» «twoˇ»
 3976        three
 3977        four
 3978    "});
 3979    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3980    cx.assert_editor_state(indoc! {"
 3981        \t«oneˇ» «twoˇ»
 3982        three
 3983        four
 3984    "});
 3985    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3986    cx.assert_editor_state(indoc! {"
 3987        \t\t«oneˇ» «twoˇ»
 3988        three
 3989        four
 3990    "});
 3991    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3992    cx.assert_editor_state(indoc! {"
 3993        \t«oneˇ» «twoˇ»
 3994        three
 3995        four
 3996    "});
 3997    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3998    cx.assert_editor_state(indoc! {"
 3999        «oneˇ» «twoˇ»
 4000        three
 4001        four
 4002    "});
 4003
 4004    // select across a line ending
 4005    cx.set_state(indoc! {"
 4006        one two
 4007        t«hree
 4008        ˇ»four
 4009    "});
 4010    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4011    cx.assert_editor_state(indoc! {"
 4012        one two
 4013        \tt«hree
 4014        ˇ»four
 4015    "});
 4016    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4017    cx.assert_editor_state(indoc! {"
 4018        one two
 4019        \t\tt«hree
 4020        ˇ»four
 4021    "});
 4022    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4023    cx.assert_editor_state(indoc! {"
 4024        one two
 4025        \tt«hree
 4026        ˇ»four
 4027    "});
 4028    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4029    cx.assert_editor_state(indoc! {"
 4030        one two
 4031        t«hree
 4032        ˇ»four
 4033    "});
 4034
 4035    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4036    cx.set_state(indoc! {"
 4037        one two
 4038        ˇthree
 4039        four
 4040    "});
 4041    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4042    cx.assert_editor_state(indoc! {"
 4043        one two
 4044        ˇthree
 4045        four
 4046    "});
 4047    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4048    cx.assert_editor_state(indoc! {"
 4049        one two
 4050        \tˇthree
 4051        four
 4052    "});
 4053    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4054    cx.assert_editor_state(indoc! {"
 4055        one two
 4056        ˇthree
 4057        four
 4058    "});
 4059}
 4060
 4061#[gpui::test]
 4062fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4063    init_test(cx, |settings| {
 4064        settings.languages.0.extend([
 4065            (
 4066                "TOML".into(),
 4067                LanguageSettingsContent {
 4068                    tab_size: NonZeroU32::new(2),
 4069                    ..Default::default()
 4070                },
 4071            ),
 4072            (
 4073                "Rust".into(),
 4074                LanguageSettingsContent {
 4075                    tab_size: NonZeroU32::new(4),
 4076                    ..Default::default()
 4077                },
 4078            ),
 4079        ]);
 4080    });
 4081
 4082    let toml_language = Arc::new(Language::new(
 4083        LanguageConfig {
 4084            name: "TOML".into(),
 4085            ..Default::default()
 4086        },
 4087        None,
 4088    ));
 4089    let rust_language = Arc::new(Language::new(
 4090        LanguageConfig {
 4091            name: "Rust".into(),
 4092            ..Default::default()
 4093        },
 4094        None,
 4095    ));
 4096
 4097    let toml_buffer =
 4098        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4099    let rust_buffer =
 4100        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4101    let multibuffer = cx.new(|cx| {
 4102        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4103        multibuffer.push_excerpts(
 4104            toml_buffer.clone(),
 4105            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4106            cx,
 4107        );
 4108        multibuffer.push_excerpts(
 4109            rust_buffer.clone(),
 4110            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4111            cx,
 4112        );
 4113        multibuffer
 4114    });
 4115
 4116    cx.add_window(|window, cx| {
 4117        let mut editor = build_editor(multibuffer, window, cx);
 4118
 4119        assert_eq!(
 4120            editor.text(cx),
 4121            indoc! {"
 4122                a = 1
 4123                b = 2
 4124
 4125                const c: usize = 3;
 4126            "}
 4127        );
 4128
 4129        select_ranges(
 4130            &mut editor,
 4131            indoc! {"
 4132                «aˇ» = 1
 4133                b = 2
 4134
 4135                «const c:ˇ» usize = 3;
 4136            "},
 4137            window,
 4138            cx,
 4139        );
 4140
 4141        editor.tab(&Tab, window, cx);
 4142        assert_text_with_selections(
 4143            &mut editor,
 4144            indoc! {"
 4145                  «aˇ» = 1
 4146                b = 2
 4147
 4148                    «const c:ˇ» usize = 3;
 4149            "},
 4150            cx,
 4151        );
 4152        editor.backtab(&Backtab, window, cx);
 4153        assert_text_with_selections(
 4154            &mut editor,
 4155            indoc! {"
 4156                «aˇ» = 1
 4157                b = 2
 4158
 4159                «const c:ˇ» usize = 3;
 4160            "},
 4161            cx,
 4162        );
 4163
 4164        editor
 4165    });
 4166}
 4167
 4168#[gpui::test]
 4169async fn test_backspace(cx: &mut TestAppContext) {
 4170    init_test(cx, |_| {});
 4171
 4172    let mut cx = EditorTestContext::new(cx).await;
 4173
 4174    // Basic backspace
 4175    cx.set_state(indoc! {"
 4176        onˇe two three
 4177        fou«rˇ» five six
 4178        seven «ˇeight nine
 4179        »ten
 4180    "});
 4181    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4182    cx.assert_editor_state(indoc! {"
 4183        oˇe two three
 4184        fouˇ five six
 4185        seven ˇten
 4186    "});
 4187
 4188    // Test backspace inside and around indents
 4189    cx.set_state(indoc! {"
 4190        zero
 4191            ˇone
 4192                ˇtwo
 4193            ˇ ˇ ˇ  three
 4194        ˇ  ˇ  four
 4195    "});
 4196    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4197    cx.assert_editor_state(indoc! {"
 4198        zero
 4199        ˇone
 4200            ˇtwo
 4201        ˇ  threeˇ  four
 4202    "});
 4203}
 4204
 4205#[gpui::test]
 4206async fn test_delete(cx: &mut TestAppContext) {
 4207    init_test(cx, |_| {});
 4208
 4209    let mut cx = EditorTestContext::new(cx).await;
 4210    cx.set_state(indoc! {"
 4211        onˇe two three
 4212        fou«rˇ» five six
 4213        seven «ˇeight nine
 4214        »ten
 4215    "});
 4216    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4217    cx.assert_editor_state(indoc! {"
 4218        onˇ two three
 4219        fouˇ five six
 4220        seven ˇten
 4221    "});
 4222}
 4223
 4224#[gpui::test]
 4225fn test_delete_line(cx: &mut TestAppContext) {
 4226    init_test(cx, |_| {});
 4227
 4228    let editor = cx.add_window(|window, cx| {
 4229        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4230        build_editor(buffer, window, cx)
 4231    });
 4232    _ = editor.update(cx, |editor, window, cx| {
 4233        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4234            s.select_display_ranges([
 4235                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4236                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4237                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4238            ])
 4239        });
 4240        editor.delete_line(&DeleteLine, window, cx);
 4241        assert_eq!(editor.display_text(cx), "ghi");
 4242        assert_eq!(
 4243            editor.selections.display_ranges(cx),
 4244            vec![
 4245                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4246                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 4247            ]
 4248        );
 4249    });
 4250
 4251    let editor = cx.add_window(|window, cx| {
 4252        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4253        build_editor(buffer, window, cx)
 4254    });
 4255    _ = editor.update(cx, |editor, window, cx| {
 4256        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4257            s.select_display_ranges([
 4258                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4259            ])
 4260        });
 4261        editor.delete_line(&DeleteLine, window, cx);
 4262        assert_eq!(editor.display_text(cx), "ghi\n");
 4263        assert_eq!(
 4264            editor.selections.display_ranges(cx),
 4265            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4266        );
 4267    });
 4268}
 4269
 4270#[gpui::test]
 4271fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4272    init_test(cx, |_| {});
 4273
 4274    cx.add_window(|window, cx| {
 4275        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4276        let mut editor = build_editor(buffer.clone(), window, cx);
 4277        let buffer = buffer.read(cx).as_singleton().unwrap();
 4278
 4279        assert_eq!(
 4280            editor.selections.ranges::<Point>(cx),
 4281            &[Point::new(0, 0)..Point::new(0, 0)]
 4282        );
 4283
 4284        // When on single line, replace newline at end by space
 4285        editor.join_lines(&JoinLines, window, cx);
 4286        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4287        assert_eq!(
 4288            editor.selections.ranges::<Point>(cx),
 4289            &[Point::new(0, 3)..Point::new(0, 3)]
 4290        );
 4291
 4292        // When multiple lines are selected, remove newlines that are spanned by the selection
 4293        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4294            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4295        });
 4296        editor.join_lines(&JoinLines, window, cx);
 4297        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4298        assert_eq!(
 4299            editor.selections.ranges::<Point>(cx),
 4300            &[Point::new(0, 11)..Point::new(0, 11)]
 4301        );
 4302
 4303        // Undo should be transactional
 4304        editor.undo(&Undo, window, cx);
 4305        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4306        assert_eq!(
 4307            editor.selections.ranges::<Point>(cx),
 4308            &[Point::new(0, 5)..Point::new(2, 2)]
 4309        );
 4310
 4311        // When joining an empty line don't insert a space
 4312        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4313            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4314        });
 4315        editor.join_lines(&JoinLines, window, cx);
 4316        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4317        assert_eq!(
 4318            editor.selections.ranges::<Point>(cx),
 4319            [Point::new(2, 3)..Point::new(2, 3)]
 4320        );
 4321
 4322        // We can remove trailing newlines
 4323        editor.join_lines(&JoinLines, window, cx);
 4324        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4325        assert_eq!(
 4326            editor.selections.ranges::<Point>(cx),
 4327            [Point::new(2, 3)..Point::new(2, 3)]
 4328        );
 4329
 4330        // We don't blow up on the last line
 4331        editor.join_lines(&JoinLines, window, cx);
 4332        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4333        assert_eq!(
 4334            editor.selections.ranges::<Point>(cx),
 4335            [Point::new(2, 3)..Point::new(2, 3)]
 4336        );
 4337
 4338        // reset to test indentation
 4339        editor.buffer.update(cx, |buffer, cx| {
 4340            buffer.edit(
 4341                [
 4342                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4343                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4344                ],
 4345                None,
 4346                cx,
 4347            )
 4348        });
 4349
 4350        // We remove any leading spaces
 4351        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4352        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4353            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4354        });
 4355        editor.join_lines(&JoinLines, window, cx);
 4356        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4357
 4358        // We don't insert a space for a line containing only spaces
 4359        editor.join_lines(&JoinLines, window, cx);
 4360        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4361
 4362        // We ignore any leading tabs
 4363        editor.join_lines(&JoinLines, window, cx);
 4364        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4365
 4366        editor
 4367    });
 4368}
 4369
 4370#[gpui::test]
 4371fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4372    init_test(cx, |_| {});
 4373
 4374    cx.add_window(|window, cx| {
 4375        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4376        let mut editor = build_editor(buffer.clone(), window, cx);
 4377        let buffer = buffer.read(cx).as_singleton().unwrap();
 4378
 4379        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4380            s.select_ranges([
 4381                Point::new(0, 2)..Point::new(1, 1),
 4382                Point::new(1, 2)..Point::new(1, 2),
 4383                Point::new(3, 1)..Point::new(3, 2),
 4384            ])
 4385        });
 4386
 4387        editor.join_lines(&JoinLines, window, cx);
 4388        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4389
 4390        assert_eq!(
 4391            editor.selections.ranges::<Point>(cx),
 4392            [
 4393                Point::new(0, 7)..Point::new(0, 7),
 4394                Point::new(1, 3)..Point::new(1, 3)
 4395            ]
 4396        );
 4397        editor
 4398    });
 4399}
 4400
 4401#[gpui::test]
 4402async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4403    init_test(cx, |_| {});
 4404
 4405    let mut cx = EditorTestContext::new(cx).await;
 4406
 4407    let diff_base = r#"
 4408        Line 0
 4409        Line 1
 4410        Line 2
 4411        Line 3
 4412        "#
 4413    .unindent();
 4414
 4415    cx.set_state(
 4416        &r#"
 4417        ˇLine 0
 4418        Line 1
 4419        Line 2
 4420        Line 3
 4421        "#
 4422        .unindent(),
 4423    );
 4424
 4425    cx.set_head_text(&diff_base);
 4426    executor.run_until_parked();
 4427
 4428    // Join lines
 4429    cx.update_editor(|editor, window, cx| {
 4430        editor.join_lines(&JoinLines, window, cx);
 4431    });
 4432    executor.run_until_parked();
 4433
 4434    cx.assert_editor_state(
 4435        &r#"
 4436        Line 0ˇ Line 1
 4437        Line 2
 4438        Line 3
 4439        "#
 4440        .unindent(),
 4441    );
 4442    // Join again
 4443    cx.update_editor(|editor, window, cx| {
 4444        editor.join_lines(&JoinLines, window, cx);
 4445    });
 4446    executor.run_until_parked();
 4447
 4448    cx.assert_editor_state(
 4449        &r#"
 4450        Line 0 Line 1ˇ Line 2
 4451        Line 3
 4452        "#
 4453        .unindent(),
 4454    );
 4455}
 4456
 4457#[gpui::test]
 4458async fn test_custom_newlines_cause_no_false_positive_diffs(
 4459    executor: BackgroundExecutor,
 4460    cx: &mut TestAppContext,
 4461) {
 4462    init_test(cx, |_| {});
 4463    let mut cx = EditorTestContext::new(cx).await;
 4464    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4465    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4466    executor.run_until_parked();
 4467
 4468    cx.update_editor(|editor, window, cx| {
 4469        let snapshot = editor.snapshot(window, cx);
 4470        assert_eq!(
 4471            snapshot
 4472                .buffer_snapshot
 4473                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4474                .collect::<Vec<_>>(),
 4475            Vec::new(),
 4476            "Should not have any diffs for files with custom newlines"
 4477        );
 4478    });
 4479}
 4480
 4481#[gpui::test]
 4482async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4483    init_test(cx, |_| {});
 4484
 4485    let mut cx = EditorTestContext::new(cx).await;
 4486
 4487    // Test sort_lines_case_insensitive()
 4488    cx.set_state(indoc! {"
 4489        «z
 4490        y
 4491        x
 4492        Z
 4493        Y
 4494        Xˇ»
 4495    "});
 4496    cx.update_editor(|e, window, cx| {
 4497        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4498    });
 4499    cx.assert_editor_state(indoc! {"
 4500        «x
 4501        X
 4502        y
 4503        Y
 4504        z
 4505        Zˇ»
 4506    "});
 4507
 4508    // Test sort_lines_by_length()
 4509    //
 4510    // Demonstrates:
 4511    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4512    // - sort is stable
 4513    cx.set_state(indoc! {"
 4514        «123
 4515        æ
 4516        12
 4517 4518        1
 4519        æˇ»
 4520    "});
 4521    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4522    cx.assert_editor_state(indoc! {"
 4523        «æ
 4524 4525        1
 4526        æ
 4527        12
 4528        123ˇ»
 4529    "});
 4530
 4531    // Test reverse_lines()
 4532    cx.set_state(indoc! {"
 4533        «5
 4534        4
 4535        3
 4536        2
 4537        1ˇ»
 4538    "});
 4539    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4540    cx.assert_editor_state(indoc! {"
 4541        «1
 4542        2
 4543        3
 4544        4
 4545        5ˇ»
 4546    "});
 4547
 4548    // Skip testing shuffle_line()
 4549
 4550    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4551    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4552
 4553    // Don't manipulate when cursor is on single line, but expand the selection
 4554    cx.set_state(indoc! {"
 4555        ddˇdd
 4556        ccc
 4557        bb
 4558        a
 4559    "});
 4560    cx.update_editor(|e, window, cx| {
 4561        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4562    });
 4563    cx.assert_editor_state(indoc! {"
 4564        «ddddˇ»
 4565        ccc
 4566        bb
 4567        a
 4568    "});
 4569
 4570    // Basic manipulate case
 4571    // Start selection moves to column 0
 4572    // End of selection shrinks to fit shorter line
 4573    cx.set_state(indoc! {"
 4574        dd«d
 4575        ccc
 4576        bb
 4577        aaaaaˇ»
 4578    "});
 4579    cx.update_editor(|e, window, cx| {
 4580        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4581    });
 4582    cx.assert_editor_state(indoc! {"
 4583        «aaaaa
 4584        bb
 4585        ccc
 4586        dddˇ»
 4587    "});
 4588
 4589    // Manipulate case with newlines
 4590    cx.set_state(indoc! {"
 4591        dd«d
 4592        ccc
 4593
 4594        bb
 4595        aaaaa
 4596
 4597        ˇ»
 4598    "});
 4599    cx.update_editor(|e, window, cx| {
 4600        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4601    });
 4602    cx.assert_editor_state(indoc! {"
 4603        «
 4604
 4605        aaaaa
 4606        bb
 4607        ccc
 4608        dddˇ»
 4609
 4610    "});
 4611
 4612    // Adding new line
 4613    cx.set_state(indoc! {"
 4614        aa«a
 4615        bbˇ»b
 4616    "});
 4617    cx.update_editor(|e, window, cx| {
 4618        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4619    });
 4620    cx.assert_editor_state(indoc! {"
 4621        «aaa
 4622        bbb
 4623        added_lineˇ»
 4624    "});
 4625
 4626    // Removing line
 4627    cx.set_state(indoc! {"
 4628        aa«a
 4629        bbbˇ»
 4630    "});
 4631    cx.update_editor(|e, window, cx| {
 4632        e.manipulate_immutable_lines(window, cx, |lines| {
 4633            lines.pop();
 4634        })
 4635    });
 4636    cx.assert_editor_state(indoc! {"
 4637        «aaaˇ»
 4638    "});
 4639
 4640    // Removing all lines
 4641    cx.set_state(indoc! {"
 4642        aa«a
 4643        bbbˇ»
 4644    "});
 4645    cx.update_editor(|e, window, cx| {
 4646        e.manipulate_immutable_lines(window, cx, |lines| {
 4647            lines.drain(..);
 4648        })
 4649    });
 4650    cx.assert_editor_state(indoc! {"
 4651        ˇ
 4652    "});
 4653}
 4654
 4655#[gpui::test]
 4656async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4657    init_test(cx, |_| {});
 4658
 4659    let mut cx = EditorTestContext::new(cx).await;
 4660
 4661    // Consider continuous selection as single selection
 4662    cx.set_state(indoc! {"
 4663        Aaa«aa
 4664        cˇ»c«c
 4665        bb
 4666        aaaˇ»aa
 4667    "});
 4668    cx.update_editor(|e, window, cx| {
 4669        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4670    });
 4671    cx.assert_editor_state(indoc! {"
 4672        «Aaaaa
 4673        ccc
 4674        bb
 4675        aaaaaˇ»
 4676    "});
 4677
 4678    cx.set_state(indoc! {"
 4679        Aaa«aa
 4680        cˇ»c«c
 4681        bb
 4682        aaaˇ»aa
 4683    "});
 4684    cx.update_editor(|e, window, cx| {
 4685        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4686    });
 4687    cx.assert_editor_state(indoc! {"
 4688        «Aaaaa
 4689        ccc
 4690        bbˇ»
 4691    "});
 4692
 4693    // Consider non continuous selection as distinct dedup operations
 4694    cx.set_state(indoc! {"
 4695        «aaaaa
 4696        bb
 4697        aaaaa
 4698        aaaaaˇ»
 4699
 4700        aaa«aaˇ»
 4701    "});
 4702    cx.update_editor(|e, window, cx| {
 4703        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4704    });
 4705    cx.assert_editor_state(indoc! {"
 4706        «aaaaa
 4707        bbˇ»
 4708
 4709        «aaaaaˇ»
 4710    "});
 4711}
 4712
 4713#[gpui::test]
 4714async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4715    init_test(cx, |_| {});
 4716
 4717    let mut cx = EditorTestContext::new(cx).await;
 4718
 4719    cx.set_state(indoc! {"
 4720        «Aaa
 4721        aAa
 4722        Aaaˇ»
 4723    "});
 4724    cx.update_editor(|e, window, cx| {
 4725        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4726    });
 4727    cx.assert_editor_state(indoc! {"
 4728        «Aaa
 4729        aAaˇ»
 4730    "});
 4731
 4732    cx.set_state(indoc! {"
 4733        «Aaa
 4734        aAa
 4735        aaAˇ»
 4736    "});
 4737    cx.update_editor(|e, window, cx| {
 4738        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4739    });
 4740    cx.assert_editor_state(indoc! {"
 4741        «Aaaˇ»
 4742    "});
 4743}
 4744
 4745#[gpui::test]
 4746async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 4747    init_test(cx, |_| {});
 4748
 4749    let mut cx = EditorTestContext::new(cx).await;
 4750
 4751    let js_language = Arc::new(Language::new(
 4752        LanguageConfig {
 4753            name: "JavaScript".into(),
 4754            wrap_characters: Some(language::WrapCharactersConfig {
 4755                start_prefix: "<".into(),
 4756                start_suffix: ">".into(),
 4757                end_prefix: "</".into(),
 4758                end_suffix: ">".into(),
 4759            }),
 4760            ..LanguageConfig::default()
 4761        },
 4762        None,
 4763    ));
 4764
 4765    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4766
 4767    cx.set_state(indoc! {"
 4768        «testˇ»
 4769    "});
 4770    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4771    cx.assert_editor_state(indoc! {"
 4772        <«ˇ»>test</«ˇ»>
 4773    "});
 4774
 4775    cx.set_state(indoc! {"
 4776        «test
 4777         testˇ»
 4778    "});
 4779    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4780    cx.assert_editor_state(indoc! {"
 4781        <«ˇ»>test
 4782         test</«ˇ»>
 4783    "});
 4784
 4785    cx.set_state(indoc! {"
 4786        teˇst
 4787    "});
 4788    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4789    cx.assert_editor_state(indoc! {"
 4790        te<«ˇ»></«ˇ»>st
 4791    "});
 4792}
 4793
 4794#[gpui::test]
 4795async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 4796    init_test(cx, |_| {});
 4797
 4798    let mut cx = EditorTestContext::new(cx).await;
 4799
 4800    let js_language = Arc::new(Language::new(
 4801        LanguageConfig {
 4802            name: "JavaScript".into(),
 4803            wrap_characters: Some(language::WrapCharactersConfig {
 4804                start_prefix: "<".into(),
 4805                start_suffix: ">".into(),
 4806                end_prefix: "</".into(),
 4807                end_suffix: ">".into(),
 4808            }),
 4809            ..LanguageConfig::default()
 4810        },
 4811        None,
 4812    ));
 4813
 4814    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4815
 4816    cx.set_state(indoc! {"
 4817        «testˇ»
 4818        «testˇ» «testˇ»
 4819        «testˇ»
 4820    "});
 4821    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4822    cx.assert_editor_state(indoc! {"
 4823        <«ˇ»>test</«ˇ»>
 4824        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 4825        <«ˇ»>test</«ˇ»>
 4826    "});
 4827
 4828    cx.set_state(indoc! {"
 4829        «test
 4830         testˇ»
 4831        «test
 4832         testˇ»
 4833    "});
 4834    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4835    cx.assert_editor_state(indoc! {"
 4836        <«ˇ»>test
 4837         test</«ˇ»>
 4838        <«ˇ»>test
 4839         test</«ˇ»>
 4840    "});
 4841}
 4842
 4843#[gpui::test]
 4844async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 4845    init_test(cx, |_| {});
 4846
 4847    let mut cx = EditorTestContext::new(cx).await;
 4848
 4849    let plaintext_language = Arc::new(Language::new(
 4850        LanguageConfig {
 4851            name: "Plain Text".into(),
 4852            ..LanguageConfig::default()
 4853        },
 4854        None,
 4855    ));
 4856
 4857    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 4858
 4859    cx.set_state(indoc! {"
 4860        «testˇ»
 4861    "});
 4862    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4863    cx.assert_editor_state(indoc! {"
 4864      «testˇ»
 4865    "});
 4866}
 4867
 4868#[gpui::test]
 4869async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4870    init_test(cx, |_| {});
 4871
 4872    let mut cx = EditorTestContext::new(cx).await;
 4873
 4874    // Manipulate with multiple selections on a single line
 4875    cx.set_state(indoc! {"
 4876        dd«dd
 4877        cˇ»c«c
 4878        bb
 4879        aaaˇ»aa
 4880    "});
 4881    cx.update_editor(|e, window, cx| {
 4882        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4883    });
 4884    cx.assert_editor_state(indoc! {"
 4885        «aaaaa
 4886        bb
 4887        ccc
 4888        ddddˇ»
 4889    "});
 4890
 4891    // Manipulate with multiple disjoin selections
 4892    cx.set_state(indoc! {"
 4893 4894        4
 4895        3
 4896        2
 4897        1ˇ»
 4898
 4899        dd«dd
 4900        ccc
 4901        bb
 4902        aaaˇ»aa
 4903    "});
 4904    cx.update_editor(|e, window, cx| {
 4905        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4906    });
 4907    cx.assert_editor_state(indoc! {"
 4908        «1
 4909        2
 4910        3
 4911        4
 4912        5ˇ»
 4913
 4914        «aaaaa
 4915        bb
 4916        ccc
 4917        ddddˇ»
 4918    "});
 4919
 4920    // Adding lines on each selection
 4921    cx.set_state(indoc! {"
 4922 4923        1ˇ»
 4924
 4925        bb«bb
 4926        aaaˇ»aa
 4927    "});
 4928    cx.update_editor(|e, window, cx| {
 4929        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4930    });
 4931    cx.assert_editor_state(indoc! {"
 4932        «2
 4933        1
 4934        added lineˇ»
 4935
 4936        «bbbb
 4937        aaaaa
 4938        added lineˇ»
 4939    "});
 4940
 4941    // Removing lines on each selection
 4942    cx.set_state(indoc! {"
 4943 4944        1ˇ»
 4945
 4946        bb«bb
 4947        aaaˇ»aa
 4948    "});
 4949    cx.update_editor(|e, window, cx| {
 4950        e.manipulate_immutable_lines(window, cx, |lines| {
 4951            lines.pop();
 4952        })
 4953    });
 4954    cx.assert_editor_state(indoc! {"
 4955        «2ˇ»
 4956
 4957        «bbbbˇ»
 4958    "});
 4959}
 4960
 4961#[gpui::test]
 4962async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4963    init_test(cx, |settings| {
 4964        settings.defaults.tab_size = NonZeroU32::new(3)
 4965    });
 4966
 4967    let mut cx = EditorTestContext::new(cx).await;
 4968
 4969    // MULTI SELECTION
 4970    // Ln.1 "«" tests empty lines
 4971    // Ln.9 tests just leading whitespace
 4972    cx.set_state(indoc! {"
 4973        «
 4974        abc                 // No indentationˇ»
 4975        «\tabc              // 1 tabˇ»
 4976        \t\tabc «      ˇ»   // 2 tabs
 4977        \t ab«c             // Tab followed by space
 4978         \tabc              // Space followed by tab (3 spaces should be the result)
 4979        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4980           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4981        \t
 4982        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4983    "});
 4984    cx.update_editor(|e, window, cx| {
 4985        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4986    });
 4987    cx.assert_editor_state(
 4988        indoc! {"
 4989            «
 4990            abc                 // No indentation
 4991               abc              // 1 tab
 4992                  abc          // 2 tabs
 4993                abc             // Tab followed by space
 4994               abc              // Space followed by tab (3 spaces should be the result)
 4995                           abc   // Mixed indentation (tab conversion depends on the column)
 4996               abc         // Already space indented
 4997               ·
 4998               abc\tdef          // Only the leading tab is manipulatedˇ»
 4999        "}
 5000        .replace("·", "")
 5001        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5002    );
 5003
 5004    // Test on just a few lines, the others should remain unchanged
 5005    // Only lines (3, 5, 10, 11) should change
 5006    cx.set_state(
 5007        indoc! {"
 5008            ·
 5009            abc                 // No indentation
 5010            \tabcˇ               // 1 tab
 5011            \t\tabc             // 2 tabs
 5012            \t abcˇ              // Tab followed by space
 5013             \tabc              // Space followed by tab (3 spaces should be the result)
 5014            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5015               abc              // Already space indented
 5016            «\t
 5017            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5018        "}
 5019        .replace("·", "")
 5020        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5021    );
 5022    cx.update_editor(|e, window, cx| {
 5023        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5024    });
 5025    cx.assert_editor_state(
 5026        indoc! {"
 5027            ·
 5028            abc                 // No indentation
 5029            «   abc               // 1 tabˇ»
 5030            \t\tabc             // 2 tabs
 5031            «    abc              // Tab followed by spaceˇ»
 5032             \tabc              // Space followed by tab (3 spaces should be the result)
 5033            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5034               abc              // Already space indented
 5035            «   ·
 5036               abc\tdef          // Only the leading tab is manipulatedˇ»
 5037        "}
 5038        .replace("·", "")
 5039        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5040    );
 5041
 5042    // SINGLE SELECTION
 5043    // Ln.1 "«" tests empty lines
 5044    // Ln.9 tests just leading whitespace
 5045    cx.set_state(indoc! {"
 5046        «
 5047        abc                 // No indentation
 5048        \tabc               // 1 tab
 5049        \t\tabc             // 2 tabs
 5050        \t abc              // Tab followed by space
 5051         \tabc              // Space followed by tab (3 spaces should be the result)
 5052        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5053           abc              // Already space indented
 5054        \t
 5055        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5056    "});
 5057    cx.update_editor(|e, window, cx| {
 5058        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5059    });
 5060    cx.assert_editor_state(
 5061        indoc! {"
 5062            «
 5063            abc                 // No indentation
 5064               abc               // 1 tab
 5065                  abc             // 2 tabs
 5066                abc              // Tab followed by space
 5067               abc              // Space followed by tab (3 spaces should be the result)
 5068                           abc   // Mixed indentation (tab conversion depends on the column)
 5069               abc              // Already space indented
 5070               ·
 5071               abc\tdef          // Only the leading tab is manipulatedˇ»
 5072        "}
 5073        .replace("·", "")
 5074        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5075    );
 5076}
 5077
 5078#[gpui::test]
 5079async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5080    init_test(cx, |settings| {
 5081        settings.defaults.tab_size = NonZeroU32::new(3)
 5082    });
 5083
 5084    let mut cx = EditorTestContext::new(cx).await;
 5085
 5086    // MULTI SELECTION
 5087    // Ln.1 "«" tests empty lines
 5088    // Ln.11 tests just leading whitespace
 5089    cx.set_state(indoc! {"
 5090        «
 5091        abˇ»ˇc                 // No indentation
 5092         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5093          abc  «             // 2 spaces (< 3 so dont convert)
 5094           abc              // 3 spaces (convert)
 5095             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5096        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5097        «\t abc              // Tab followed by space
 5098         \tabc              // Space followed by tab (should be consumed due to tab)
 5099        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5100           \tˇ»  «\t
 5101           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5102    "});
 5103    cx.update_editor(|e, window, cx| {
 5104        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5105    });
 5106    cx.assert_editor_state(indoc! {"
 5107        «
 5108        abc                 // No indentation
 5109         abc                // 1 space (< 3 so dont convert)
 5110          abc               // 2 spaces (< 3 so dont convert)
 5111        \tabc              // 3 spaces (convert)
 5112        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5113        \t\t\tabc           // Already tab indented
 5114        \t abc              // Tab followed by space
 5115        \tabc              // Space followed by tab (should be consumed due to tab)
 5116        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5117        \t\t\t
 5118        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5119    "});
 5120
 5121    // Test on just a few lines, the other should remain unchanged
 5122    // Only lines (4, 8, 11, 12) should change
 5123    cx.set_state(
 5124        indoc! {"
 5125            ·
 5126            abc                 // No indentation
 5127             abc                // 1 space (< 3 so dont convert)
 5128              abc               // 2 spaces (< 3 so dont convert)
 5129            «   abc              // 3 spaces (convert)ˇ»
 5130                 abc            // 5 spaces (1 tab + 2 spaces)
 5131            \t\t\tabc           // Already tab indented
 5132            \t abc              // Tab followed by space
 5133             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5134               \t\t  \tabc      // Mixed indentation
 5135            \t \t  \t   \tabc   // Mixed indentation
 5136               \t  \tˇ
 5137            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5138        "}
 5139        .replace("·", "")
 5140        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5141    );
 5142    cx.update_editor(|e, window, cx| {
 5143        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5144    });
 5145    cx.assert_editor_state(
 5146        indoc! {"
 5147            ·
 5148            abc                 // No indentation
 5149             abc                // 1 space (< 3 so dont convert)
 5150              abc               // 2 spaces (< 3 so dont convert)
 5151            «\tabc              // 3 spaces (convert)ˇ»
 5152                 abc            // 5 spaces (1 tab + 2 spaces)
 5153            \t\t\tabc           // Already tab indented
 5154            \t abc              // Tab followed by space
 5155            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5156               \t\t  \tabc      // Mixed indentation
 5157            \t \t  \t   \tabc   // Mixed indentation
 5158            «\t\t\t
 5159            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5160        "}
 5161        .replace("·", "")
 5162        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5163    );
 5164
 5165    // SINGLE SELECTION
 5166    // Ln.1 "«" tests empty lines
 5167    // Ln.11 tests just leading whitespace
 5168    cx.set_state(indoc! {"
 5169        «
 5170        abc                 // No indentation
 5171         abc                // 1 space (< 3 so dont convert)
 5172          abc               // 2 spaces (< 3 so dont convert)
 5173           abc              // 3 spaces (convert)
 5174             abc            // 5 spaces (1 tab + 2 spaces)
 5175        \t\t\tabc           // Already tab indented
 5176        \t abc              // Tab followed by space
 5177         \tabc              // Space followed by tab (should be consumed due to tab)
 5178        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5179           \t  \t
 5180           abc   \t         // Only the leading spaces should be convertedˇ»
 5181    "});
 5182    cx.update_editor(|e, window, cx| {
 5183        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5184    });
 5185    cx.assert_editor_state(indoc! {"
 5186        «
 5187        abc                 // No indentation
 5188         abc                // 1 space (< 3 so dont convert)
 5189          abc               // 2 spaces (< 3 so dont convert)
 5190        \tabc              // 3 spaces (convert)
 5191        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5192        \t\t\tabc           // Already tab indented
 5193        \t abc              // Tab followed by space
 5194        \tabc              // Space followed by tab (should be consumed due to tab)
 5195        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5196        \t\t\t
 5197        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5198    "});
 5199}
 5200
 5201#[gpui::test]
 5202async fn test_toggle_case(cx: &mut TestAppContext) {
 5203    init_test(cx, |_| {});
 5204
 5205    let mut cx = EditorTestContext::new(cx).await;
 5206
 5207    // If all lower case -> upper case
 5208    cx.set_state(indoc! {"
 5209        «hello worldˇ»
 5210    "});
 5211    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5212    cx.assert_editor_state(indoc! {"
 5213        «HELLO WORLDˇ»
 5214    "});
 5215
 5216    // If all upper case -> lower case
 5217    cx.set_state(indoc! {"
 5218        «HELLO WORLDˇ»
 5219    "});
 5220    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5221    cx.assert_editor_state(indoc! {"
 5222        «hello worldˇ»
 5223    "});
 5224
 5225    // If any upper case characters are identified -> lower case
 5226    // This matches JetBrains IDEs
 5227    cx.set_state(indoc! {"
 5228        «hEllo worldˇ»
 5229    "});
 5230    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5231    cx.assert_editor_state(indoc! {"
 5232        «hello worldˇ»
 5233    "});
 5234}
 5235
 5236#[gpui::test]
 5237async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5238    init_test(cx, |_| {});
 5239
 5240    let mut cx = EditorTestContext::new(cx).await;
 5241
 5242    cx.set_state(indoc! {"
 5243        «implement-windows-supportˇ»
 5244    "});
 5245    cx.update_editor(|e, window, cx| {
 5246        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5247    });
 5248    cx.assert_editor_state(indoc! {"
 5249        «Implement windows supportˇ»
 5250    "});
 5251}
 5252
 5253#[gpui::test]
 5254async fn test_manipulate_text(cx: &mut TestAppContext) {
 5255    init_test(cx, |_| {});
 5256
 5257    let mut cx = EditorTestContext::new(cx).await;
 5258
 5259    // Test convert_to_upper_case()
 5260    cx.set_state(indoc! {"
 5261        «hello worldˇ»
 5262    "});
 5263    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5264    cx.assert_editor_state(indoc! {"
 5265        «HELLO WORLDˇ»
 5266    "});
 5267
 5268    // Test convert_to_lower_case()
 5269    cx.set_state(indoc! {"
 5270        «HELLO WORLDˇ»
 5271    "});
 5272    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5273    cx.assert_editor_state(indoc! {"
 5274        «hello worldˇ»
 5275    "});
 5276
 5277    // Test multiple line, single selection case
 5278    cx.set_state(indoc! {"
 5279        «The quick brown
 5280        fox jumps over
 5281        the lazy dogˇ»
 5282    "});
 5283    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5284    cx.assert_editor_state(indoc! {"
 5285        «The Quick Brown
 5286        Fox Jumps Over
 5287        The Lazy Dogˇ»
 5288    "});
 5289
 5290    // Test multiple line, single selection case
 5291    cx.set_state(indoc! {"
 5292        «The quick brown
 5293        fox jumps over
 5294        the lazy dogˇ»
 5295    "});
 5296    cx.update_editor(|e, window, cx| {
 5297        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5298    });
 5299    cx.assert_editor_state(indoc! {"
 5300        «TheQuickBrown
 5301        FoxJumpsOver
 5302        TheLazyDogˇ»
 5303    "});
 5304
 5305    // From here on out, test more complex cases of manipulate_text()
 5306
 5307    // Test no selection case - should affect words cursors are in
 5308    // Cursor at beginning, middle, and end of word
 5309    cx.set_state(indoc! {"
 5310        ˇhello big beauˇtiful worldˇ
 5311    "});
 5312    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5313    cx.assert_editor_state(indoc! {"
 5314        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5315    "});
 5316
 5317    // Test multiple selections on a single line and across multiple lines
 5318    cx.set_state(indoc! {"
 5319        «Theˇ» quick «brown
 5320        foxˇ» jumps «overˇ»
 5321        the «lazyˇ» dog
 5322    "});
 5323    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5324    cx.assert_editor_state(indoc! {"
 5325        «THEˇ» quick «BROWN
 5326        FOXˇ» jumps «OVERˇ»
 5327        the «LAZYˇ» dog
 5328    "});
 5329
 5330    // Test case where text length grows
 5331    cx.set_state(indoc! {"
 5332        «tschüߡ»
 5333    "});
 5334    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5335    cx.assert_editor_state(indoc! {"
 5336        «TSCHÜSSˇ»
 5337    "});
 5338
 5339    // Test to make sure we don't crash when text shrinks
 5340    cx.set_state(indoc! {"
 5341        aaa_bbbˇ
 5342    "});
 5343    cx.update_editor(|e, window, cx| {
 5344        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5345    });
 5346    cx.assert_editor_state(indoc! {"
 5347        «aaaBbbˇ»
 5348    "});
 5349
 5350    // Test to make sure we all aware of the fact that each word can grow and shrink
 5351    // Final selections should be aware of this fact
 5352    cx.set_state(indoc! {"
 5353        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5354    "});
 5355    cx.update_editor(|e, window, cx| {
 5356        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5357    });
 5358    cx.assert_editor_state(indoc! {"
 5359        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5360    "});
 5361
 5362    cx.set_state(indoc! {"
 5363        «hElLo, WoRld!ˇ»
 5364    "});
 5365    cx.update_editor(|e, window, cx| {
 5366        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5367    });
 5368    cx.assert_editor_state(indoc! {"
 5369        «HeLlO, wOrLD!ˇ»
 5370    "});
 5371
 5372    // Test selections with `line_mode = true`.
 5373    cx.update_editor(|editor, _window, _cx| editor.selections.line_mode = true);
 5374    cx.set_state(indoc! {"
 5375        «The quick brown
 5376        fox jumps over
 5377        tˇ»he lazy dog
 5378    "});
 5379    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5380    cx.assert_editor_state(indoc! {"
 5381        «THE QUICK BROWN
 5382        FOX JUMPS OVER
 5383        THE LAZY DOGˇ»
 5384    "});
 5385}
 5386
 5387#[gpui::test]
 5388fn test_duplicate_line(cx: &mut TestAppContext) {
 5389    init_test(cx, |_| {});
 5390
 5391    let editor = cx.add_window(|window, cx| {
 5392        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5393        build_editor(buffer, window, cx)
 5394    });
 5395    _ = editor.update(cx, |editor, window, cx| {
 5396        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5397            s.select_display_ranges([
 5398                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5399                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5400                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5401                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5402            ])
 5403        });
 5404        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5405        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5406        assert_eq!(
 5407            editor.selections.display_ranges(cx),
 5408            vec![
 5409                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5410                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5411                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5412                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5413            ]
 5414        );
 5415    });
 5416
 5417    let editor = cx.add_window(|window, cx| {
 5418        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5419        build_editor(buffer, window, cx)
 5420    });
 5421    _ = editor.update(cx, |editor, window, cx| {
 5422        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5423            s.select_display_ranges([
 5424                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5425                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5426            ])
 5427        });
 5428        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5429        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5430        assert_eq!(
 5431            editor.selections.display_ranges(cx),
 5432            vec![
 5433                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5434                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5435            ]
 5436        );
 5437    });
 5438
 5439    // With `move_upwards` the selections stay in place, except for
 5440    // the lines inserted above them
 5441    let editor = cx.add_window(|window, cx| {
 5442        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5443        build_editor(buffer, window, cx)
 5444    });
 5445    _ = editor.update(cx, |editor, window, cx| {
 5446        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5447            s.select_display_ranges([
 5448                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5449                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5450                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5451                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5452            ])
 5453        });
 5454        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5455        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5456        assert_eq!(
 5457            editor.selections.display_ranges(cx),
 5458            vec![
 5459                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5460                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5461                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5462                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5463            ]
 5464        );
 5465    });
 5466
 5467    let editor = cx.add_window(|window, cx| {
 5468        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5469        build_editor(buffer, window, cx)
 5470    });
 5471    _ = editor.update(cx, |editor, window, cx| {
 5472        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5473            s.select_display_ranges([
 5474                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5475                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5476            ])
 5477        });
 5478        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5479        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5480        assert_eq!(
 5481            editor.selections.display_ranges(cx),
 5482            vec![
 5483                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5484                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5485            ]
 5486        );
 5487    });
 5488
 5489    let editor = cx.add_window(|window, cx| {
 5490        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5491        build_editor(buffer, window, cx)
 5492    });
 5493    _ = editor.update(cx, |editor, window, cx| {
 5494        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5495            s.select_display_ranges([
 5496                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5497                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5498            ])
 5499        });
 5500        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5501        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5502        assert_eq!(
 5503            editor.selections.display_ranges(cx),
 5504            vec![
 5505                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5506                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5507            ]
 5508        );
 5509    });
 5510}
 5511
 5512#[gpui::test]
 5513fn test_move_line_up_down(cx: &mut TestAppContext) {
 5514    init_test(cx, |_| {});
 5515
 5516    let editor = cx.add_window(|window, cx| {
 5517        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5518        build_editor(buffer, window, cx)
 5519    });
 5520    _ = editor.update(cx, |editor, window, cx| {
 5521        editor.fold_creases(
 5522            vec![
 5523                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5524                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5525                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5526            ],
 5527            true,
 5528            window,
 5529            cx,
 5530        );
 5531        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5532            s.select_display_ranges([
 5533                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5534                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5535                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5536                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5537            ])
 5538        });
 5539        assert_eq!(
 5540            editor.display_text(cx),
 5541            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5542        );
 5543
 5544        editor.move_line_up(&MoveLineUp, window, cx);
 5545        assert_eq!(
 5546            editor.display_text(cx),
 5547            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5548        );
 5549        assert_eq!(
 5550            editor.selections.display_ranges(cx),
 5551            vec![
 5552                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5553                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5554                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5555                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5556            ]
 5557        );
 5558    });
 5559
 5560    _ = editor.update(cx, |editor, window, cx| {
 5561        editor.move_line_down(&MoveLineDown, window, cx);
 5562        assert_eq!(
 5563            editor.display_text(cx),
 5564            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5565        );
 5566        assert_eq!(
 5567            editor.selections.display_ranges(cx),
 5568            vec![
 5569                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5570                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5571                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5572                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5573            ]
 5574        );
 5575    });
 5576
 5577    _ = editor.update(cx, |editor, window, cx| {
 5578        editor.move_line_down(&MoveLineDown, window, cx);
 5579        assert_eq!(
 5580            editor.display_text(cx),
 5581            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5582        );
 5583        assert_eq!(
 5584            editor.selections.display_ranges(cx),
 5585            vec![
 5586                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5587                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5588                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5589                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5590            ]
 5591        );
 5592    });
 5593
 5594    _ = editor.update(cx, |editor, window, cx| {
 5595        editor.move_line_up(&MoveLineUp, window, cx);
 5596        assert_eq!(
 5597            editor.display_text(cx),
 5598            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5599        );
 5600        assert_eq!(
 5601            editor.selections.display_ranges(cx),
 5602            vec![
 5603                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5604                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5605                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5606                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5607            ]
 5608        );
 5609    });
 5610}
 5611
 5612#[gpui::test]
 5613fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5614    init_test(cx, |_| {});
 5615    let editor = cx.add_window(|window, cx| {
 5616        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5617        build_editor(buffer, window, cx)
 5618    });
 5619    _ = editor.update(cx, |editor, window, cx| {
 5620        editor.fold_creases(
 5621            vec![Crease::simple(
 5622                Point::new(6, 4)..Point::new(7, 4),
 5623                FoldPlaceholder::test(),
 5624            )],
 5625            true,
 5626            window,
 5627            cx,
 5628        );
 5629        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5630            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5631        });
 5632        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5633        editor.move_line_up(&MoveLineUp, window, cx);
 5634        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5635        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5636    });
 5637}
 5638
 5639#[gpui::test]
 5640fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5641    init_test(cx, |_| {});
 5642
 5643    let editor = cx.add_window(|window, cx| {
 5644        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5645        build_editor(buffer, window, cx)
 5646    });
 5647    _ = editor.update(cx, |editor, window, cx| {
 5648        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5649        editor.insert_blocks(
 5650            [BlockProperties {
 5651                style: BlockStyle::Fixed,
 5652                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5653                height: Some(1),
 5654                render: Arc::new(|_| div().into_any()),
 5655                priority: 0,
 5656            }],
 5657            Some(Autoscroll::fit()),
 5658            cx,
 5659        );
 5660        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5661            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5662        });
 5663        editor.move_line_down(&MoveLineDown, window, cx);
 5664    });
 5665}
 5666
 5667#[gpui::test]
 5668async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5669    init_test(cx, |_| {});
 5670
 5671    let mut cx = EditorTestContext::new(cx).await;
 5672    cx.set_state(
 5673        &"
 5674            ˇzero
 5675            one
 5676            two
 5677            three
 5678            four
 5679            five
 5680        "
 5681        .unindent(),
 5682    );
 5683
 5684    // Create a four-line block that replaces three lines of text.
 5685    cx.update_editor(|editor, window, cx| {
 5686        let snapshot = editor.snapshot(window, cx);
 5687        let snapshot = &snapshot.buffer_snapshot;
 5688        let placement = BlockPlacement::Replace(
 5689            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5690        );
 5691        editor.insert_blocks(
 5692            [BlockProperties {
 5693                placement,
 5694                height: Some(4),
 5695                style: BlockStyle::Sticky,
 5696                render: Arc::new(|_| gpui::div().into_any_element()),
 5697                priority: 0,
 5698            }],
 5699            None,
 5700            cx,
 5701        );
 5702    });
 5703
 5704    // Move down so that the cursor touches the block.
 5705    cx.update_editor(|editor, window, cx| {
 5706        editor.move_down(&Default::default(), window, cx);
 5707    });
 5708    cx.assert_editor_state(
 5709        &"
 5710            zero
 5711            «one
 5712            two
 5713            threeˇ»
 5714            four
 5715            five
 5716        "
 5717        .unindent(),
 5718    );
 5719
 5720    // Move down past the block.
 5721    cx.update_editor(|editor, window, cx| {
 5722        editor.move_down(&Default::default(), window, cx);
 5723    });
 5724    cx.assert_editor_state(
 5725        &"
 5726            zero
 5727            one
 5728            two
 5729            three
 5730            ˇfour
 5731            five
 5732        "
 5733        .unindent(),
 5734    );
 5735}
 5736
 5737#[gpui::test]
 5738fn test_transpose(cx: &mut TestAppContext) {
 5739    init_test(cx, |_| {});
 5740
 5741    _ = cx.add_window(|window, cx| {
 5742        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5743        editor.set_style(EditorStyle::default(), window, cx);
 5744        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5745            s.select_ranges([1..1])
 5746        });
 5747        editor.transpose(&Default::default(), window, cx);
 5748        assert_eq!(editor.text(cx), "bac");
 5749        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5750
 5751        editor.transpose(&Default::default(), window, cx);
 5752        assert_eq!(editor.text(cx), "bca");
 5753        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5754
 5755        editor.transpose(&Default::default(), window, cx);
 5756        assert_eq!(editor.text(cx), "bac");
 5757        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5758
 5759        editor
 5760    });
 5761
 5762    _ = cx.add_window(|window, cx| {
 5763        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5764        editor.set_style(EditorStyle::default(), window, cx);
 5765        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5766            s.select_ranges([3..3])
 5767        });
 5768        editor.transpose(&Default::default(), window, cx);
 5769        assert_eq!(editor.text(cx), "acb\nde");
 5770        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5771
 5772        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5773            s.select_ranges([4..4])
 5774        });
 5775        editor.transpose(&Default::default(), window, cx);
 5776        assert_eq!(editor.text(cx), "acbd\ne");
 5777        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5778
 5779        editor.transpose(&Default::default(), window, cx);
 5780        assert_eq!(editor.text(cx), "acbde\n");
 5781        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5782
 5783        editor.transpose(&Default::default(), window, cx);
 5784        assert_eq!(editor.text(cx), "acbd\ne");
 5785        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5786
 5787        editor
 5788    });
 5789
 5790    _ = cx.add_window(|window, cx| {
 5791        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5792        editor.set_style(EditorStyle::default(), window, cx);
 5793        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5794            s.select_ranges([1..1, 2..2, 4..4])
 5795        });
 5796        editor.transpose(&Default::default(), window, cx);
 5797        assert_eq!(editor.text(cx), "bacd\ne");
 5798        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5799
 5800        editor.transpose(&Default::default(), window, cx);
 5801        assert_eq!(editor.text(cx), "bcade\n");
 5802        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5803
 5804        editor.transpose(&Default::default(), window, cx);
 5805        assert_eq!(editor.text(cx), "bcda\ne");
 5806        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5807
 5808        editor.transpose(&Default::default(), window, cx);
 5809        assert_eq!(editor.text(cx), "bcade\n");
 5810        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5811
 5812        editor.transpose(&Default::default(), window, cx);
 5813        assert_eq!(editor.text(cx), "bcaed\n");
 5814        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5815
 5816        editor
 5817    });
 5818
 5819    _ = cx.add_window(|window, cx| {
 5820        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5821        editor.set_style(EditorStyle::default(), window, cx);
 5822        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5823            s.select_ranges([4..4])
 5824        });
 5825        editor.transpose(&Default::default(), window, cx);
 5826        assert_eq!(editor.text(cx), "🏀🍐✋");
 5827        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5828
 5829        editor.transpose(&Default::default(), window, cx);
 5830        assert_eq!(editor.text(cx), "🏀✋🍐");
 5831        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5832
 5833        editor.transpose(&Default::default(), window, cx);
 5834        assert_eq!(editor.text(cx), "🏀🍐✋");
 5835        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5836
 5837        editor
 5838    });
 5839}
 5840
 5841#[gpui::test]
 5842async fn test_rewrap(cx: &mut TestAppContext) {
 5843    init_test(cx, |settings| {
 5844        settings.languages.0.extend([
 5845            (
 5846                "Markdown".into(),
 5847                LanguageSettingsContent {
 5848                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5849                    preferred_line_length: Some(40),
 5850                    ..Default::default()
 5851                },
 5852            ),
 5853            (
 5854                "Plain Text".into(),
 5855                LanguageSettingsContent {
 5856                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5857                    preferred_line_length: Some(40),
 5858                    ..Default::default()
 5859                },
 5860            ),
 5861            (
 5862                "C++".into(),
 5863                LanguageSettingsContent {
 5864                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5865                    preferred_line_length: Some(40),
 5866                    ..Default::default()
 5867                },
 5868            ),
 5869            (
 5870                "Python".into(),
 5871                LanguageSettingsContent {
 5872                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5873                    preferred_line_length: Some(40),
 5874                    ..Default::default()
 5875                },
 5876            ),
 5877            (
 5878                "Rust".into(),
 5879                LanguageSettingsContent {
 5880                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5881                    preferred_line_length: Some(40),
 5882                    ..Default::default()
 5883                },
 5884            ),
 5885        ])
 5886    });
 5887
 5888    let mut cx = EditorTestContext::new(cx).await;
 5889
 5890    let cpp_language = Arc::new(Language::new(
 5891        LanguageConfig {
 5892            name: "C++".into(),
 5893            line_comments: vec!["// ".into()],
 5894            ..LanguageConfig::default()
 5895        },
 5896        None,
 5897    ));
 5898    let python_language = Arc::new(Language::new(
 5899        LanguageConfig {
 5900            name: "Python".into(),
 5901            line_comments: vec!["# ".into()],
 5902            ..LanguageConfig::default()
 5903        },
 5904        None,
 5905    ));
 5906    let markdown_language = Arc::new(Language::new(
 5907        LanguageConfig {
 5908            name: "Markdown".into(),
 5909            rewrap_prefixes: vec![
 5910                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5911                regex::Regex::new("[-*+]\\s+").unwrap(),
 5912            ],
 5913            ..LanguageConfig::default()
 5914        },
 5915        None,
 5916    ));
 5917    let rust_language = Arc::new(
 5918        Language::new(
 5919            LanguageConfig {
 5920                name: "Rust".into(),
 5921                line_comments: vec!["// ".into(), "/// ".into()],
 5922                ..LanguageConfig::default()
 5923            },
 5924            Some(tree_sitter_rust::LANGUAGE.into()),
 5925        )
 5926        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 5927        .unwrap(),
 5928    );
 5929
 5930    let plaintext_language = Arc::new(Language::new(
 5931        LanguageConfig {
 5932            name: "Plain Text".into(),
 5933            ..LanguageConfig::default()
 5934        },
 5935        None,
 5936    ));
 5937
 5938    // Test basic rewrapping of a long line with a cursor
 5939    assert_rewrap(
 5940        indoc! {"
 5941            // ˇThis is a long comment that needs to be wrapped.
 5942        "},
 5943        indoc! {"
 5944            // ˇThis is a long comment that needs to
 5945            // be wrapped.
 5946        "},
 5947        cpp_language.clone(),
 5948        &mut cx,
 5949    );
 5950
 5951    // Test rewrapping a full selection
 5952    assert_rewrap(
 5953        indoc! {"
 5954            «// This selected long comment needs to be wrapped.ˇ»"
 5955        },
 5956        indoc! {"
 5957            «// This selected long comment needs to
 5958            // be wrapped.ˇ»"
 5959        },
 5960        cpp_language.clone(),
 5961        &mut cx,
 5962    );
 5963
 5964    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5965    assert_rewrap(
 5966        indoc! {"
 5967            // ˇThis is the first line.
 5968            // Thisˇ is the second line.
 5969            // This is the thirdˇ line, all part of one paragraph.
 5970         "},
 5971        indoc! {"
 5972            // ˇThis is the first line. Thisˇ is the
 5973            // second line. This is the thirdˇ line,
 5974            // all part of one paragraph.
 5975         "},
 5976        cpp_language.clone(),
 5977        &mut cx,
 5978    );
 5979
 5980    // Test multiple cursors in different paragraphs trigger separate rewraps
 5981    assert_rewrap(
 5982        indoc! {"
 5983            // ˇThis is the first paragraph, first line.
 5984            // ˇThis is the first paragraph, second line.
 5985
 5986            // ˇThis is the second paragraph, first line.
 5987            // ˇThis is the second paragraph, second line.
 5988        "},
 5989        indoc! {"
 5990            // ˇThis is the first paragraph, first
 5991            // line. ˇThis is the first paragraph,
 5992            // second line.
 5993
 5994            // ˇThis is the second paragraph, first
 5995            // line. ˇThis is the second paragraph,
 5996            // second line.
 5997        "},
 5998        cpp_language.clone(),
 5999        &mut cx,
 6000    );
 6001
 6002    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6003    assert_rewrap(
 6004        indoc! {"
 6005            «// A regular long long comment to be wrapped.
 6006            /// A documentation long comment to be wrapped.ˇ»
 6007          "},
 6008        indoc! {"
 6009            «// A regular long long comment to be
 6010            // wrapped.
 6011            /// A documentation long comment to be
 6012            /// wrapped.ˇ»
 6013          "},
 6014        rust_language.clone(),
 6015        &mut cx,
 6016    );
 6017
 6018    // Test that change in indentation level trigger seperate rewraps
 6019    assert_rewrap(
 6020        indoc! {"
 6021            fn foo() {
 6022                «// This is a long comment at the base indent.
 6023                    // This is a long comment at the next indent.ˇ»
 6024            }
 6025        "},
 6026        indoc! {"
 6027            fn foo() {
 6028                «// This is a long comment at the
 6029                // base indent.
 6030                    // This is a long comment at the
 6031                    // next indent.ˇ»
 6032            }
 6033        "},
 6034        rust_language.clone(),
 6035        &mut cx,
 6036    );
 6037
 6038    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6039    assert_rewrap(
 6040        indoc! {"
 6041            # ˇThis is a long comment using a pound sign.
 6042        "},
 6043        indoc! {"
 6044            # ˇThis is a long comment using a pound
 6045            # sign.
 6046        "},
 6047        python_language,
 6048        &mut cx,
 6049    );
 6050
 6051    // Test rewrapping only affects comments, not code even when selected
 6052    assert_rewrap(
 6053        indoc! {"
 6054            «/// This doc comment is long and should be wrapped.
 6055            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6056        "},
 6057        indoc! {"
 6058            «/// This doc comment is long and should
 6059            /// be wrapped.
 6060            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6061        "},
 6062        rust_language.clone(),
 6063        &mut cx,
 6064    );
 6065
 6066    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6067    assert_rewrap(
 6068        indoc! {"
 6069            # Header
 6070
 6071            A long long long line of markdown text to wrap.ˇ
 6072         "},
 6073        indoc! {"
 6074            # Header
 6075
 6076            A long long long line of markdown text
 6077            to wrap.ˇ
 6078         "},
 6079        markdown_language.clone(),
 6080        &mut cx,
 6081    );
 6082
 6083    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6084    assert_rewrap(
 6085        indoc! {"
 6086            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6087            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6088            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6089        "},
 6090        indoc! {"
 6091            «1. This is a numbered list item that is
 6092               very long and needs to be wrapped
 6093               properly.
 6094            2. This is a numbered list item that is
 6095               very long and needs to be wrapped
 6096               properly.
 6097            - This is an unordered list item that is
 6098              also very long and should not merge
 6099              with the numbered item.ˇ»
 6100        "},
 6101        markdown_language.clone(),
 6102        &mut cx,
 6103    );
 6104
 6105    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6106    assert_rewrap(
 6107        indoc! {"
 6108            «1. This is a numbered list item that is
 6109            very long and needs to be wrapped
 6110            properly.
 6111            2. This is a numbered list item that is
 6112            very long and needs to be wrapped
 6113            properly.
 6114            - This is an unordered list item that is
 6115            also very long and should not merge with
 6116            the numbered item.ˇ»
 6117        "},
 6118        indoc! {"
 6119            «1. This is a numbered list item that is
 6120               very long and needs to be wrapped
 6121               properly.
 6122            2. This is a numbered list item that is
 6123               very long and needs to be wrapped
 6124               properly.
 6125            - This is an unordered list item that is
 6126              also very long and should not merge
 6127              with the numbered item.ˇ»
 6128        "},
 6129        markdown_language.clone(),
 6130        &mut cx,
 6131    );
 6132
 6133    // Test that rewrapping maintain indents even when they already exists.
 6134    assert_rewrap(
 6135        indoc! {"
 6136            «1. This is a numbered list
 6137               item that is very long and needs to be wrapped properly.
 6138            2. This is a numbered list
 6139               item that is very long and needs to be wrapped properly.
 6140            - This is an unordered list item that is also very long and
 6141              should not merge with the numbered item.ˇ»
 6142        "},
 6143        indoc! {"
 6144            «1. This is a numbered list item that is
 6145               very long and needs to be wrapped
 6146               properly.
 6147            2. This is a numbered list item that is
 6148               very long and needs to be wrapped
 6149               properly.
 6150            - This is an unordered list item that is
 6151              also very long and should not merge
 6152              with the numbered item.ˇ»
 6153        "},
 6154        markdown_language,
 6155        &mut cx,
 6156    );
 6157
 6158    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6159    assert_rewrap(
 6160        indoc! {"
 6161            ˇThis is a very long line of plain text that will be wrapped.
 6162        "},
 6163        indoc! {"
 6164            ˇThis is a very long line of plain text
 6165            that will be wrapped.
 6166        "},
 6167        plaintext_language.clone(),
 6168        &mut cx,
 6169    );
 6170
 6171    // Test that non-commented code acts as a paragraph boundary within a selection
 6172    assert_rewrap(
 6173        indoc! {"
 6174               «// This is the first long comment block to be wrapped.
 6175               fn my_func(a: u32);
 6176               // This is the second long comment block to be wrapped.ˇ»
 6177           "},
 6178        indoc! {"
 6179               «// This is the first long comment block
 6180               // to be wrapped.
 6181               fn my_func(a: u32);
 6182               // This is the second long comment block
 6183               // to be wrapped.ˇ»
 6184           "},
 6185        rust_language,
 6186        &mut cx,
 6187    );
 6188
 6189    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6190    assert_rewrap(
 6191        indoc! {"
 6192            «ˇThis is a very long line that will be wrapped.
 6193
 6194            This is another paragraph in the same selection.»
 6195
 6196            «\tThis is a very long indented line that will be wrapped.ˇ»
 6197         "},
 6198        indoc! {"
 6199            «ˇThis is a very long line that will be
 6200            wrapped.
 6201
 6202            This is another paragraph in the same
 6203            selection.»
 6204
 6205            «\tThis is a very long indented line
 6206            \tthat will be wrapped.ˇ»
 6207         "},
 6208        plaintext_language,
 6209        &mut cx,
 6210    );
 6211
 6212    // Test that an empty comment line acts as a paragraph boundary
 6213    assert_rewrap(
 6214        indoc! {"
 6215            // ˇThis is a long comment that will be wrapped.
 6216            //
 6217            // And this is another long comment that will also be wrapped.ˇ
 6218         "},
 6219        indoc! {"
 6220            // ˇThis is a long comment that will be
 6221            // wrapped.
 6222            //
 6223            // And this is another long comment that
 6224            // will also be wrapped.ˇ
 6225         "},
 6226        cpp_language,
 6227        &mut cx,
 6228    );
 6229
 6230    #[track_caller]
 6231    fn assert_rewrap(
 6232        unwrapped_text: &str,
 6233        wrapped_text: &str,
 6234        language: Arc<Language>,
 6235        cx: &mut EditorTestContext,
 6236    ) {
 6237        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6238        cx.set_state(unwrapped_text);
 6239        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6240        cx.assert_editor_state(wrapped_text);
 6241    }
 6242}
 6243
 6244#[gpui::test]
 6245async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6246    init_test(cx, |settings| {
 6247        settings.languages.0.extend([(
 6248            "Rust".into(),
 6249            LanguageSettingsContent {
 6250                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6251                preferred_line_length: Some(40),
 6252                ..Default::default()
 6253            },
 6254        )])
 6255    });
 6256
 6257    let mut cx = EditorTestContext::new(cx).await;
 6258
 6259    let rust_lang = Arc::new(
 6260        Language::new(
 6261            LanguageConfig {
 6262                name: "Rust".into(),
 6263                line_comments: vec!["// ".into()],
 6264                block_comment: Some(BlockCommentConfig {
 6265                    start: "/*".into(),
 6266                    end: "*/".into(),
 6267                    prefix: "* ".into(),
 6268                    tab_size: 1,
 6269                }),
 6270                documentation_comment: Some(BlockCommentConfig {
 6271                    start: "/**".into(),
 6272                    end: "*/".into(),
 6273                    prefix: "* ".into(),
 6274                    tab_size: 1,
 6275                }),
 6276
 6277                ..LanguageConfig::default()
 6278            },
 6279            Some(tree_sitter_rust::LANGUAGE.into()),
 6280        )
 6281        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6282        .unwrap(),
 6283    );
 6284
 6285    // regular block comment
 6286    assert_rewrap(
 6287        indoc! {"
 6288            /*
 6289             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6290             */
 6291            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6292        "},
 6293        indoc! {"
 6294            /*
 6295             *ˇ Lorem ipsum dolor sit amet,
 6296             * consectetur adipiscing elit.
 6297             */
 6298            /*
 6299             *ˇ Lorem ipsum dolor sit amet,
 6300             * consectetur adipiscing elit.
 6301             */
 6302        "},
 6303        rust_lang.clone(),
 6304        &mut cx,
 6305    );
 6306
 6307    // indent is respected
 6308    assert_rewrap(
 6309        indoc! {"
 6310            {}
 6311                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6312        "},
 6313        indoc! {"
 6314            {}
 6315                /*
 6316                 *ˇ Lorem ipsum dolor sit amet,
 6317                 * consectetur adipiscing elit.
 6318                 */
 6319        "},
 6320        rust_lang.clone(),
 6321        &mut cx,
 6322    );
 6323
 6324    // short block comments with inline delimiters
 6325    assert_rewrap(
 6326        indoc! {"
 6327            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6328            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6329             */
 6330            /*
 6331             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6332        "},
 6333        indoc! {"
 6334            /*
 6335             *ˇ Lorem ipsum dolor sit amet,
 6336             * consectetur adipiscing elit.
 6337             */
 6338            /*
 6339             *ˇ Lorem ipsum dolor sit amet,
 6340             * consectetur adipiscing elit.
 6341             */
 6342            /*
 6343             *ˇ Lorem ipsum dolor sit amet,
 6344             * consectetur adipiscing elit.
 6345             */
 6346        "},
 6347        rust_lang.clone(),
 6348        &mut cx,
 6349    );
 6350
 6351    // multiline block comment with inline start/end delimiters
 6352    assert_rewrap(
 6353        indoc! {"
 6354            /*ˇ Lorem ipsum dolor sit amet,
 6355             * consectetur adipiscing elit. */
 6356        "},
 6357        indoc! {"
 6358            /*
 6359             *ˇ Lorem ipsum dolor sit amet,
 6360             * consectetur adipiscing elit.
 6361             */
 6362        "},
 6363        rust_lang.clone(),
 6364        &mut cx,
 6365    );
 6366
 6367    // block comment rewrap still respects paragraph bounds
 6368    assert_rewrap(
 6369        indoc! {"
 6370            /*
 6371             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6372             *
 6373             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6374             */
 6375        "},
 6376        indoc! {"
 6377            /*
 6378             *ˇ Lorem ipsum dolor sit amet,
 6379             * consectetur adipiscing elit.
 6380             *
 6381             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6382             */
 6383        "},
 6384        rust_lang.clone(),
 6385        &mut cx,
 6386    );
 6387
 6388    // documentation comments
 6389    assert_rewrap(
 6390        indoc! {"
 6391            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6392            /**
 6393             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6394             */
 6395        "},
 6396        indoc! {"
 6397            /**
 6398             *ˇ Lorem ipsum dolor sit amet,
 6399             * consectetur adipiscing elit.
 6400             */
 6401            /**
 6402             *ˇ Lorem ipsum dolor sit amet,
 6403             * consectetur adipiscing elit.
 6404             */
 6405        "},
 6406        rust_lang.clone(),
 6407        &mut cx,
 6408    );
 6409
 6410    // different, adjacent comments
 6411    assert_rewrap(
 6412        indoc! {"
 6413            /**
 6414             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6415             */
 6416            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6417            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6418        "},
 6419        indoc! {"
 6420            /**
 6421             *ˇ Lorem ipsum dolor sit amet,
 6422             * consectetur adipiscing elit.
 6423             */
 6424            /*
 6425             *ˇ Lorem ipsum dolor sit amet,
 6426             * consectetur adipiscing elit.
 6427             */
 6428            //ˇ Lorem ipsum dolor sit amet,
 6429            // consectetur adipiscing elit.
 6430        "},
 6431        rust_lang.clone(),
 6432        &mut cx,
 6433    );
 6434
 6435    // selection w/ single short block comment
 6436    assert_rewrap(
 6437        indoc! {"
 6438            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6439        "},
 6440        indoc! {"
 6441            «/*
 6442             * Lorem ipsum dolor sit amet,
 6443             * consectetur adipiscing elit.
 6444             */ˇ»
 6445        "},
 6446        rust_lang.clone(),
 6447        &mut cx,
 6448    );
 6449
 6450    // rewrapping a single comment w/ abutting comments
 6451    assert_rewrap(
 6452        indoc! {"
 6453            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6454            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6455        "},
 6456        indoc! {"
 6457            /*
 6458             * ˇLorem ipsum dolor sit amet,
 6459             * consectetur adipiscing elit.
 6460             */
 6461            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6462        "},
 6463        rust_lang.clone(),
 6464        &mut cx,
 6465    );
 6466
 6467    // selection w/ non-abutting short block comments
 6468    assert_rewrap(
 6469        indoc! {"
 6470            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6471
 6472            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6473        "},
 6474        indoc! {"
 6475            «/*
 6476             * Lorem ipsum dolor sit amet,
 6477             * consectetur adipiscing elit.
 6478             */
 6479
 6480            /*
 6481             * Lorem ipsum dolor sit amet,
 6482             * consectetur adipiscing elit.
 6483             */ˇ»
 6484        "},
 6485        rust_lang.clone(),
 6486        &mut cx,
 6487    );
 6488
 6489    // selection of multiline block comments
 6490    assert_rewrap(
 6491        indoc! {"
 6492            «/* Lorem ipsum dolor sit amet,
 6493             * consectetur adipiscing elit. */ˇ»
 6494        "},
 6495        indoc! {"
 6496            «/*
 6497             * Lorem ipsum dolor sit amet,
 6498             * consectetur adipiscing elit.
 6499             */ˇ»
 6500        "},
 6501        rust_lang.clone(),
 6502        &mut cx,
 6503    );
 6504
 6505    // partial selection of multiline block comments
 6506    assert_rewrap(
 6507        indoc! {"
 6508            «/* Lorem ipsum dolor sit amet,ˇ»
 6509             * consectetur adipiscing elit. */
 6510            /* Lorem ipsum dolor sit amet,
 6511             «* consectetur adipiscing elit. */ˇ»
 6512        "},
 6513        indoc! {"
 6514            «/*
 6515             * Lorem ipsum dolor sit amet,ˇ»
 6516             * consectetur adipiscing elit. */
 6517            /* Lorem ipsum dolor sit amet,
 6518             «* consectetur adipiscing elit.
 6519             */ˇ»
 6520        "},
 6521        rust_lang.clone(),
 6522        &mut cx,
 6523    );
 6524
 6525    // selection w/ abutting short block comments
 6526    // TODO: should not be combined; should rewrap as 2 comments
 6527    assert_rewrap(
 6528        indoc! {"
 6529            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6530            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6531        "},
 6532        // desired behavior:
 6533        // indoc! {"
 6534        //     «/*
 6535        //      * Lorem ipsum dolor sit amet,
 6536        //      * consectetur adipiscing elit.
 6537        //      */
 6538        //     /*
 6539        //      * Lorem ipsum dolor sit amet,
 6540        //      * consectetur adipiscing elit.
 6541        //      */ˇ»
 6542        // "},
 6543        // actual behaviour:
 6544        indoc! {"
 6545            «/*
 6546             * Lorem ipsum dolor sit amet,
 6547             * consectetur adipiscing elit. Lorem
 6548             * ipsum dolor sit amet, consectetur
 6549             * adipiscing elit.
 6550             */ˇ»
 6551        "},
 6552        rust_lang.clone(),
 6553        &mut cx,
 6554    );
 6555
 6556    // TODO: same as above, but with delimiters on separate line
 6557    // assert_rewrap(
 6558    //     indoc! {"
 6559    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6560    //          */
 6561    //         /*
 6562    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6563    //     "},
 6564    //     // desired:
 6565    //     // indoc! {"
 6566    //     //     «/*
 6567    //     //      * Lorem ipsum dolor sit amet,
 6568    //     //      * consectetur adipiscing elit.
 6569    //     //      */
 6570    //     //     /*
 6571    //     //      * Lorem ipsum dolor sit amet,
 6572    //     //      * consectetur adipiscing elit.
 6573    //     //      */ˇ»
 6574    //     // "},
 6575    //     // actual: (but with trailing w/s on the empty lines)
 6576    //     indoc! {"
 6577    //         «/*
 6578    //          * Lorem ipsum dolor sit amet,
 6579    //          * consectetur adipiscing elit.
 6580    //          *
 6581    //          */
 6582    //         /*
 6583    //          *
 6584    //          * Lorem ipsum dolor sit amet,
 6585    //          * consectetur adipiscing elit.
 6586    //          */ˇ»
 6587    //     "},
 6588    //     rust_lang.clone(),
 6589    //     &mut cx,
 6590    // );
 6591
 6592    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6593    assert_rewrap(
 6594        indoc! {"
 6595            /*
 6596             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6597             */
 6598            /*
 6599             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6600            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6601        "},
 6602        // desired:
 6603        // indoc! {"
 6604        //     /*
 6605        //      *ˇ Lorem ipsum dolor sit amet,
 6606        //      * consectetur adipiscing elit.
 6607        //      */
 6608        //     /*
 6609        //      *ˇ Lorem ipsum dolor sit amet,
 6610        //      * consectetur adipiscing elit.
 6611        //      */
 6612        //     /*
 6613        //      *ˇ Lorem ipsum dolor sit amet
 6614        //      */ /* consectetur adipiscing elit. */
 6615        // "},
 6616        // actual:
 6617        indoc! {"
 6618            /*
 6619             //ˇ Lorem ipsum dolor sit amet,
 6620             // consectetur adipiscing elit.
 6621             */
 6622            /*
 6623             * //ˇ Lorem ipsum dolor sit amet,
 6624             * consectetur adipiscing elit.
 6625             */
 6626            /*
 6627             *ˇ Lorem ipsum dolor sit amet */ /*
 6628             * consectetur adipiscing elit.
 6629             */
 6630        "},
 6631        rust_lang,
 6632        &mut cx,
 6633    );
 6634
 6635    #[track_caller]
 6636    fn assert_rewrap(
 6637        unwrapped_text: &str,
 6638        wrapped_text: &str,
 6639        language: Arc<Language>,
 6640        cx: &mut EditorTestContext,
 6641    ) {
 6642        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6643        cx.set_state(unwrapped_text);
 6644        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6645        cx.assert_editor_state(wrapped_text);
 6646    }
 6647}
 6648
 6649#[gpui::test]
 6650async fn test_hard_wrap(cx: &mut TestAppContext) {
 6651    init_test(cx, |_| {});
 6652    let mut cx = EditorTestContext::new(cx).await;
 6653
 6654    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6655    cx.update_editor(|editor, _, cx| {
 6656        editor.set_hard_wrap(Some(14), cx);
 6657    });
 6658
 6659    cx.set_state(indoc!(
 6660        "
 6661        one two three ˇ
 6662        "
 6663    ));
 6664    cx.simulate_input("four");
 6665    cx.run_until_parked();
 6666
 6667    cx.assert_editor_state(indoc!(
 6668        "
 6669        one two three
 6670        fourˇ
 6671        "
 6672    ));
 6673
 6674    cx.update_editor(|editor, window, cx| {
 6675        editor.newline(&Default::default(), window, cx);
 6676    });
 6677    cx.run_until_parked();
 6678    cx.assert_editor_state(indoc!(
 6679        "
 6680        one two three
 6681        four
 6682        ˇ
 6683        "
 6684    ));
 6685
 6686    cx.simulate_input("five");
 6687    cx.run_until_parked();
 6688    cx.assert_editor_state(indoc!(
 6689        "
 6690        one two three
 6691        four
 6692        fiveˇ
 6693        "
 6694    ));
 6695
 6696    cx.update_editor(|editor, window, cx| {
 6697        editor.newline(&Default::default(), window, cx);
 6698    });
 6699    cx.run_until_parked();
 6700    cx.simulate_input("# ");
 6701    cx.run_until_parked();
 6702    cx.assert_editor_state(indoc!(
 6703        "
 6704        one two three
 6705        four
 6706        five
 6707        # ˇ
 6708        "
 6709    ));
 6710
 6711    cx.update_editor(|editor, window, cx| {
 6712        editor.newline(&Default::default(), window, cx);
 6713    });
 6714    cx.run_until_parked();
 6715    cx.assert_editor_state(indoc!(
 6716        "
 6717        one two three
 6718        four
 6719        five
 6720        #\x20
 6721 6722        "
 6723    ));
 6724
 6725    cx.simulate_input(" 6");
 6726    cx.run_until_parked();
 6727    cx.assert_editor_state(indoc!(
 6728        "
 6729        one two three
 6730        four
 6731        five
 6732        #
 6733        # 6ˇ
 6734        "
 6735    ));
 6736}
 6737
 6738#[gpui::test]
 6739async fn test_cut_line_ends(cx: &mut TestAppContext) {
 6740    init_test(cx, |_| {});
 6741
 6742    let mut cx = EditorTestContext::new(cx).await;
 6743
 6744    cx.set_state(indoc! {"
 6745        The quick« brownˇ»
 6746        fox jumps overˇ
 6747        the lazy dog"});
 6748    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6749    cx.assert_editor_state(indoc! {"
 6750        The quickˇ
 6751        ˇthe lazy dog"});
 6752
 6753    cx.set_state(indoc! {"
 6754        The quick« brownˇ»
 6755        fox jumps overˇ
 6756        the lazy dog"});
 6757    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 6758    cx.assert_editor_state(indoc! {"
 6759        The quickˇ
 6760        fox jumps overˇthe lazy dog"});
 6761
 6762    cx.set_state(indoc! {"
 6763        The quick« brownˇ»
 6764        fox jumps overˇ
 6765        the lazy dog"});
 6766    cx.update_editor(|e, window, cx| {
 6767        e.cut_to_end_of_line(
 6768            &CutToEndOfLine {
 6769                stop_at_newlines: true,
 6770            },
 6771            window,
 6772            cx,
 6773        )
 6774    });
 6775    cx.assert_editor_state(indoc! {"
 6776        The quickˇ
 6777        fox jumps overˇ
 6778        the lazy dog"});
 6779
 6780    cx.set_state(indoc! {"
 6781        The quick« brownˇ»
 6782        fox jumps overˇ
 6783        the lazy dog"});
 6784    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 6785    cx.assert_editor_state(indoc! {"
 6786        The quickˇ
 6787        fox jumps overˇthe lazy dog"});
 6788}
 6789
 6790#[gpui::test]
 6791async fn test_clipboard(cx: &mut TestAppContext) {
 6792    init_test(cx, |_| {});
 6793
 6794    let mut cx = EditorTestContext::new(cx).await;
 6795
 6796    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 6797    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6798    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 6799
 6800    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 6801    cx.set_state("two ˇfour ˇsix ˇ");
 6802    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6803    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 6804
 6805    // Paste again but with only two cursors. Since the number of cursors doesn't
 6806    // match the number of slices in the clipboard, the entire clipboard text
 6807    // is pasted at each cursor.
 6808    cx.set_state("ˇtwo one✅ four three six five ˇ");
 6809    cx.update_editor(|e, window, cx| {
 6810        e.handle_input("( ", window, cx);
 6811        e.paste(&Paste, window, cx);
 6812        e.handle_input(") ", window, cx);
 6813    });
 6814    cx.assert_editor_state(
 6815        &([
 6816            "( one✅ ",
 6817            "three ",
 6818            "five ) ˇtwo one✅ four three six five ( one✅ ",
 6819            "three ",
 6820            "five ) ˇ",
 6821        ]
 6822        .join("\n")),
 6823    );
 6824
 6825    // Cut with three selections, one of which is full-line.
 6826    cx.set_state(indoc! {"
 6827        1«2ˇ»3
 6828        4ˇ567
 6829        «8ˇ»9"});
 6830    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6831    cx.assert_editor_state(indoc! {"
 6832        1ˇ3
 6833        ˇ9"});
 6834
 6835    // Paste with three selections, noticing how the copied selection that was full-line
 6836    // gets inserted before the second cursor.
 6837    cx.set_state(indoc! {"
 6838        1ˇ3
 6839 6840        «oˇ»ne"});
 6841    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6842    cx.assert_editor_state(indoc! {"
 6843        12ˇ3
 6844        4567
 6845 6846        8ˇne"});
 6847
 6848    // Copy with a single cursor only, which writes the whole line into the clipboard.
 6849    cx.set_state(indoc! {"
 6850        The quick brown
 6851        fox juˇmps over
 6852        the lazy dog"});
 6853    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6854    assert_eq!(
 6855        cx.read_from_clipboard()
 6856            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6857        Some("fox jumps over\n".to_string())
 6858    );
 6859
 6860    // Paste with three selections, noticing how the copied full-line selection is inserted
 6861    // before the empty selections but replaces the selection that is non-empty.
 6862    cx.set_state(indoc! {"
 6863        Tˇhe quick brown
 6864        «foˇ»x jumps over
 6865        tˇhe lazy dog"});
 6866    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6867    cx.assert_editor_state(indoc! {"
 6868        fox jumps over
 6869        Tˇhe quick brown
 6870        fox jumps over
 6871        ˇx jumps over
 6872        fox jumps over
 6873        tˇhe lazy dog"});
 6874}
 6875
 6876#[gpui::test]
 6877async fn test_copy_trim(cx: &mut TestAppContext) {
 6878    init_test(cx, |_| {});
 6879
 6880    let mut cx = EditorTestContext::new(cx).await;
 6881    cx.set_state(
 6882        r#"            «for selection in selections.iter() {
 6883            let mut start = selection.start;
 6884            let mut end = selection.end;
 6885            let is_entire_line = selection.is_empty();
 6886            if is_entire_line {
 6887                start = Point::new(start.row, 0);ˇ»
 6888                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6889            }
 6890        "#,
 6891    );
 6892    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6893    assert_eq!(
 6894        cx.read_from_clipboard()
 6895            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6896        Some(
 6897            "for selection in selections.iter() {
 6898            let mut start = selection.start;
 6899            let mut end = selection.end;
 6900            let is_entire_line = selection.is_empty();
 6901            if is_entire_line {
 6902                start = Point::new(start.row, 0);"
 6903                .to_string()
 6904        ),
 6905        "Regular copying preserves all indentation selected",
 6906    );
 6907    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6908    assert_eq!(
 6909        cx.read_from_clipboard()
 6910            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6911        Some(
 6912            "for selection in selections.iter() {
 6913let mut start = selection.start;
 6914let mut end = selection.end;
 6915let is_entire_line = selection.is_empty();
 6916if is_entire_line {
 6917    start = Point::new(start.row, 0);"
 6918                .to_string()
 6919        ),
 6920        "Copying with stripping should strip all leading whitespaces"
 6921    );
 6922
 6923    cx.set_state(
 6924        r#"       «     for selection in selections.iter() {
 6925            let mut start = selection.start;
 6926            let mut end = selection.end;
 6927            let is_entire_line = selection.is_empty();
 6928            if is_entire_line {
 6929                start = Point::new(start.row, 0);ˇ»
 6930                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6931            }
 6932        "#,
 6933    );
 6934    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6935    assert_eq!(
 6936        cx.read_from_clipboard()
 6937            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6938        Some(
 6939            "     for selection in selections.iter() {
 6940            let mut start = selection.start;
 6941            let mut end = selection.end;
 6942            let is_entire_line = selection.is_empty();
 6943            if is_entire_line {
 6944                start = Point::new(start.row, 0);"
 6945                .to_string()
 6946        ),
 6947        "Regular copying preserves all indentation selected",
 6948    );
 6949    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6950    assert_eq!(
 6951        cx.read_from_clipboard()
 6952            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6953        Some(
 6954            "for selection in selections.iter() {
 6955let mut start = selection.start;
 6956let mut end = selection.end;
 6957let is_entire_line = selection.is_empty();
 6958if is_entire_line {
 6959    start = Point::new(start.row, 0);"
 6960                .to_string()
 6961        ),
 6962        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 6963    );
 6964
 6965    cx.set_state(
 6966        r#"       «ˇ     for selection in selections.iter() {
 6967            let mut start = selection.start;
 6968            let mut end = selection.end;
 6969            let is_entire_line = selection.is_empty();
 6970            if is_entire_line {
 6971                start = Point::new(start.row, 0);»
 6972                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6973            }
 6974        "#,
 6975    );
 6976    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6977    assert_eq!(
 6978        cx.read_from_clipboard()
 6979            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6980        Some(
 6981            "     for selection in selections.iter() {
 6982            let mut start = selection.start;
 6983            let mut end = selection.end;
 6984            let is_entire_line = selection.is_empty();
 6985            if is_entire_line {
 6986                start = Point::new(start.row, 0);"
 6987                .to_string()
 6988        ),
 6989        "Regular copying for reverse selection works the same",
 6990    );
 6991    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6992    assert_eq!(
 6993        cx.read_from_clipboard()
 6994            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6995        Some(
 6996            "for selection in selections.iter() {
 6997let mut start = selection.start;
 6998let mut end = selection.end;
 6999let is_entire_line = selection.is_empty();
 7000if is_entire_line {
 7001    start = Point::new(start.row, 0);"
 7002                .to_string()
 7003        ),
 7004        "Copying with stripping for reverse selection works the same"
 7005    );
 7006
 7007    cx.set_state(
 7008        r#"            for selection «in selections.iter() {
 7009            let mut start = selection.start;
 7010            let mut end = selection.end;
 7011            let is_entire_line = selection.is_empty();
 7012            if is_entire_line {
 7013                start = Point::new(start.row, 0);ˇ»
 7014                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7015            }
 7016        "#,
 7017    );
 7018    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7019    assert_eq!(
 7020        cx.read_from_clipboard()
 7021            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7022        Some(
 7023            "in selections.iter() {
 7024            let mut start = selection.start;
 7025            let mut end = selection.end;
 7026            let is_entire_line = selection.is_empty();
 7027            if is_entire_line {
 7028                start = Point::new(start.row, 0);"
 7029                .to_string()
 7030        ),
 7031        "When selecting past the indent, the copying works as usual",
 7032    );
 7033    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7034    assert_eq!(
 7035        cx.read_from_clipboard()
 7036            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7037        Some(
 7038            "in selections.iter() {
 7039            let mut start = selection.start;
 7040            let mut end = selection.end;
 7041            let is_entire_line = selection.is_empty();
 7042            if is_entire_line {
 7043                start = Point::new(start.row, 0);"
 7044                .to_string()
 7045        ),
 7046        "When selecting past the indent, nothing is trimmed"
 7047    );
 7048
 7049    cx.set_state(
 7050        r#"            «for selection in selections.iter() {
 7051            let mut start = selection.start;
 7052
 7053            let mut end = selection.end;
 7054            let is_entire_line = selection.is_empty();
 7055            if is_entire_line {
 7056                start = Point::new(start.row, 0);
 7057ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7058            }
 7059        "#,
 7060    );
 7061    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7062    assert_eq!(
 7063        cx.read_from_clipboard()
 7064            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7065        Some(
 7066            "for selection in selections.iter() {
 7067let mut start = selection.start;
 7068
 7069let mut end = selection.end;
 7070let is_entire_line = selection.is_empty();
 7071if is_entire_line {
 7072    start = Point::new(start.row, 0);
 7073"
 7074            .to_string()
 7075        ),
 7076        "Copying with stripping should ignore empty lines"
 7077    );
 7078}
 7079
 7080#[gpui::test]
 7081async fn test_paste_multiline(cx: &mut TestAppContext) {
 7082    init_test(cx, |_| {});
 7083
 7084    let mut cx = EditorTestContext::new(cx).await;
 7085    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7086
 7087    // Cut an indented block, without the leading whitespace.
 7088    cx.set_state(indoc! {"
 7089        const a: B = (
 7090            c(),
 7091            «d(
 7092                e,
 7093                f
 7094            )ˇ»
 7095        );
 7096    "});
 7097    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7098    cx.assert_editor_state(indoc! {"
 7099        const a: B = (
 7100            c(),
 7101            ˇ
 7102        );
 7103    "});
 7104
 7105    // Paste it at the same position.
 7106    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7107    cx.assert_editor_state(indoc! {"
 7108        const a: B = (
 7109            c(),
 7110            d(
 7111                e,
 7112                f
 7113 7114        );
 7115    "});
 7116
 7117    // Paste it at a line with a lower indent level.
 7118    cx.set_state(indoc! {"
 7119        ˇ
 7120        const a: B = (
 7121            c(),
 7122        );
 7123    "});
 7124    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7125    cx.assert_editor_state(indoc! {"
 7126        d(
 7127            e,
 7128            f
 7129 7130        const a: B = (
 7131            c(),
 7132        );
 7133    "});
 7134
 7135    // Cut an indented block, with the leading whitespace.
 7136    cx.set_state(indoc! {"
 7137        const a: B = (
 7138            c(),
 7139        «    d(
 7140                e,
 7141                f
 7142            )
 7143        ˇ»);
 7144    "});
 7145    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7146    cx.assert_editor_state(indoc! {"
 7147        const a: B = (
 7148            c(),
 7149        ˇ);
 7150    "});
 7151
 7152    // Paste it at the same position.
 7153    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7154    cx.assert_editor_state(indoc! {"
 7155        const a: B = (
 7156            c(),
 7157            d(
 7158                e,
 7159                f
 7160            )
 7161        ˇ);
 7162    "});
 7163
 7164    // Paste it at a line with a higher indent level.
 7165    cx.set_state(indoc! {"
 7166        const a: B = (
 7167            c(),
 7168            d(
 7169                e,
 7170 7171            )
 7172        );
 7173    "});
 7174    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7175    cx.assert_editor_state(indoc! {"
 7176        const a: B = (
 7177            c(),
 7178            d(
 7179                e,
 7180                f    d(
 7181                    e,
 7182                    f
 7183                )
 7184        ˇ
 7185            )
 7186        );
 7187    "});
 7188
 7189    // Copy an indented block, starting mid-line
 7190    cx.set_state(indoc! {"
 7191        const a: B = (
 7192            c(),
 7193            somethin«g(
 7194                e,
 7195                f
 7196            )ˇ»
 7197        );
 7198    "});
 7199    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7200
 7201    // Paste it on a line with a lower indent level
 7202    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7203    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7204    cx.assert_editor_state(indoc! {"
 7205        const a: B = (
 7206            c(),
 7207            something(
 7208                e,
 7209                f
 7210            )
 7211        );
 7212        g(
 7213            e,
 7214            f
 7215"});
 7216}
 7217
 7218#[gpui::test]
 7219async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7220    init_test(cx, |_| {});
 7221
 7222    cx.write_to_clipboard(ClipboardItem::new_string(
 7223        "    d(\n        e\n    );\n".into(),
 7224    ));
 7225
 7226    let mut cx = EditorTestContext::new(cx).await;
 7227    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7228
 7229    cx.set_state(indoc! {"
 7230        fn a() {
 7231            b();
 7232            if c() {
 7233                ˇ
 7234            }
 7235        }
 7236    "});
 7237
 7238    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7239    cx.assert_editor_state(indoc! {"
 7240        fn a() {
 7241            b();
 7242            if c() {
 7243                d(
 7244                    e
 7245                );
 7246        ˇ
 7247            }
 7248        }
 7249    "});
 7250
 7251    cx.set_state(indoc! {"
 7252        fn a() {
 7253            b();
 7254            ˇ
 7255        }
 7256    "});
 7257
 7258    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7259    cx.assert_editor_state(indoc! {"
 7260        fn a() {
 7261            b();
 7262            d(
 7263                e
 7264            );
 7265        ˇ
 7266        }
 7267    "});
 7268}
 7269
 7270#[gpui::test]
 7271fn test_select_all(cx: &mut TestAppContext) {
 7272    init_test(cx, |_| {});
 7273
 7274    let editor = cx.add_window(|window, cx| {
 7275        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7276        build_editor(buffer, window, cx)
 7277    });
 7278    _ = editor.update(cx, |editor, window, cx| {
 7279        editor.select_all(&SelectAll, window, cx);
 7280        assert_eq!(
 7281            editor.selections.display_ranges(cx),
 7282            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7283        );
 7284    });
 7285}
 7286
 7287#[gpui::test]
 7288fn test_select_line(cx: &mut TestAppContext) {
 7289    init_test(cx, |_| {});
 7290
 7291    let editor = cx.add_window(|window, cx| {
 7292        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7293        build_editor(buffer, window, cx)
 7294    });
 7295    _ = editor.update(cx, |editor, window, cx| {
 7296        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7297            s.select_display_ranges([
 7298                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7299                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7300                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7301                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7302            ])
 7303        });
 7304        editor.select_line(&SelectLine, window, cx);
 7305        assert_eq!(
 7306            editor.selections.display_ranges(cx),
 7307            vec![
 7308                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7309                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7310            ]
 7311        );
 7312    });
 7313
 7314    _ = editor.update(cx, |editor, window, cx| {
 7315        editor.select_line(&SelectLine, window, cx);
 7316        assert_eq!(
 7317            editor.selections.display_ranges(cx),
 7318            vec![
 7319                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7320                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7321            ]
 7322        );
 7323    });
 7324
 7325    _ = editor.update(cx, |editor, window, cx| {
 7326        editor.select_line(&SelectLine, window, cx);
 7327        assert_eq!(
 7328            editor.selections.display_ranges(cx),
 7329            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7330        );
 7331    });
 7332}
 7333
 7334#[gpui::test]
 7335async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7336    init_test(cx, |_| {});
 7337    let mut cx = EditorTestContext::new(cx).await;
 7338
 7339    #[track_caller]
 7340    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7341        cx.set_state(initial_state);
 7342        cx.update_editor(|e, window, cx| {
 7343            e.split_selection_into_lines(&Default::default(), window, cx)
 7344        });
 7345        cx.assert_editor_state(expected_state);
 7346    }
 7347
 7348    // Selection starts and ends at the middle of lines, left-to-right
 7349    test(
 7350        &mut cx,
 7351        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7352        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7353    );
 7354    // Same thing, right-to-left
 7355    test(
 7356        &mut cx,
 7357        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7358        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7359    );
 7360
 7361    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7362    test(
 7363        &mut cx,
 7364        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7365        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7366    );
 7367    // Same thing, right-to-left
 7368    test(
 7369        &mut cx,
 7370        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7371        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7372    );
 7373
 7374    // Whole buffer, left-to-right, last line ends with newline
 7375    test(
 7376        &mut cx,
 7377        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7378        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7379    );
 7380    // Same thing, right-to-left
 7381    test(
 7382        &mut cx,
 7383        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7384        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7385    );
 7386
 7387    // Starts at the end of a line, ends at the start of another
 7388    test(
 7389        &mut cx,
 7390        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7391        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7392    );
 7393}
 7394
 7395#[gpui::test]
 7396async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7397    init_test(cx, |_| {});
 7398
 7399    let editor = cx.add_window(|window, cx| {
 7400        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7401        build_editor(buffer, window, cx)
 7402    });
 7403
 7404    // setup
 7405    _ = editor.update(cx, |editor, window, cx| {
 7406        editor.fold_creases(
 7407            vec![
 7408                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7409                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7410                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7411            ],
 7412            true,
 7413            window,
 7414            cx,
 7415        );
 7416        assert_eq!(
 7417            editor.display_text(cx),
 7418            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7419        );
 7420    });
 7421
 7422    _ = editor.update(cx, |editor, window, cx| {
 7423        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7424            s.select_display_ranges([
 7425                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7426                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7427                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7428                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7429            ])
 7430        });
 7431        editor.split_selection_into_lines(&Default::default(), window, cx);
 7432        assert_eq!(
 7433            editor.display_text(cx),
 7434            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7435        );
 7436    });
 7437    EditorTestContext::for_editor(editor, cx)
 7438        .await
 7439        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7440
 7441    _ = editor.update(cx, |editor, window, cx| {
 7442        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7443            s.select_display_ranges([
 7444                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7445            ])
 7446        });
 7447        editor.split_selection_into_lines(&Default::default(), window, cx);
 7448        assert_eq!(
 7449            editor.display_text(cx),
 7450            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7451        );
 7452        assert_eq!(
 7453            editor.selections.display_ranges(cx),
 7454            [
 7455                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7456                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7457                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7458                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7459                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7460                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7461                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7462            ]
 7463        );
 7464    });
 7465    EditorTestContext::for_editor(editor, cx)
 7466        .await
 7467        .assert_editor_state(
 7468            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7469        );
 7470}
 7471
 7472#[gpui::test]
 7473async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7474    init_test(cx, |_| {});
 7475
 7476    let mut cx = EditorTestContext::new(cx).await;
 7477
 7478    cx.set_state(indoc!(
 7479        r#"abc
 7480           defˇghi
 7481
 7482           jk
 7483           nlmo
 7484           "#
 7485    ));
 7486
 7487    cx.update_editor(|editor, window, cx| {
 7488        editor.add_selection_above(&Default::default(), window, cx);
 7489    });
 7490
 7491    cx.assert_editor_state(indoc!(
 7492        r#"abcˇ
 7493           defˇghi
 7494
 7495           jk
 7496           nlmo
 7497           "#
 7498    ));
 7499
 7500    cx.update_editor(|editor, window, cx| {
 7501        editor.add_selection_above(&Default::default(), window, cx);
 7502    });
 7503
 7504    cx.assert_editor_state(indoc!(
 7505        r#"abcˇ
 7506            defˇghi
 7507
 7508            jk
 7509            nlmo
 7510            "#
 7511    ));
 7512
 7513    cx.update_editor(|editor, window, cx| {
 7514        editor.add_selection_below(&Default::default(), window, cx);
 7515    });
 7516
 7517    cx.assert_editor_state(indoc!(
 7518        r#"abc
 7519           defˇghi
 7520
 7521           jk
 7522           nlmo
 7523           "#
 7524    ));
 7525
 7526    cx.update_editor(|editor, window, cx| {
 7527        editor.undo_selection(&Default::default(), window, cx);
 7528    });
 7529
 7530    cx.assert_editor_state(indoc!(
 7531        r#"abcˇ
 7532           defˇghi
 7533
 7534           jk
 7535           nlmo
 7536           "#
 7537    ));
 7538
 7539    cx.update_editor(|editor, window, cx| {
 7540        editor.redo_selection(&Default::default(), window, cx);
 7541    });
 7542
 7543    cx.assert_editor_state(indoc!(
 7544        r#"abc
 7545           defˇghi
 7546
 7547           jk
 7548           nlmo
 7549           "#
 7550    ));
 7551
 7552    cx.update_editor(|editor, window, cx| {
 7553        editor.add_selection_below(&Default::default(), window, cx);
 7554    });
 7555
 7556    cx.assert_editor_state(indoc!(
 7557        r#"abc
 7558           defˇghi
 7559           ˇ
 7560           jk
 7561           nlmo
 7562           "#
 7563    ));
 7564
 7565    cx.update_editor(|editor, window, cx| {
 7566        editor.add_selection_below(&Default::default(), window, cx);
 7567    });
 7568
 7569    cx.assert_editor_state(indoc!(
 7570        r#"abc
 7571           defˇghi
 7572           ˇ
 7573           jkˇ
 7574           nlmo
 7575           "#
 7576    ));
 7577
 7578    cx.update_editor(|editor, window, cx| {
 7579        editor.add_selection_below(&Default::default(), window, cx);
 7580    });
 7581
 7582    cx.assert_editor_state(indoc!(
 7583        r#"abc
 7584           defˇghi
 7585           ˇ
 7586           jkˇ
 7587           nlmˇo
 7588           "#
 7589    ));
 7590
 7591    cx.update_editor(|editor, window, cx| {
 7592        editor.add_selection_below(&Default::default(), window, cx);
 7593    });
 7594
 7595    cx.assert_editor_state(indoc!(
 7596        r#"abc
 7597           defˇghi
 7598           ˇ
 7599           jkˇ
 7600           nlmˇo
 7601           ˇ"#
 7602    ));
 7603
 7604    // change selections
 7605    cx.set_state(indoc!(
 7606        r#"abc
 7607           def«ˇg»hi
 7608
 7609           jk
 7610           nlmo
 7611           "#
 7612    ));
 7613
 7614    cx.update_editor(|editor, window, cx| {
 7615        editor.add_selection_below(&Default::default(), window, cx);
 7616    });
 7617
 7618    cx.assert_editor_state(indoc!(
 7619        r#"abc
 7620           def«ˇg»hi
 7621
 7622           jk
 7623           nlm«ˇo»
 7624           "#
 7625    ));
 7626
 7627    cx.update_editor(|editor, window, cx| {
 7628        editor.add_selection_below(&Default::default(), window, cx);
 7629    });
 7630
 7631    cx.assert_editor_state(indoc!(
 7632        r#"abc
 7633           def«ˇg»hi
 7634
 7635           jk
 7636           nlm«ˇo»
 7637           "#
 7638    ));
 7639
 7640    cx.update_editor(|editor, window, cx| {
 7641        editor.add_selection_above(&Default::default(), window, cx);
 7642    });
 7643
 7644    cx.assert_editor_state(indoc!(
 7645        r#"abc
 7646           def«ˇg»hi
 7647
 7648           jk
 7649           nlmo
 7650           "#
 7651    ));
 7652
 7653    cx.update_editor(|editor, window, cx| {
 7654        editor.add_selection_above(&Default::default(), window, cx);
 7655    });
 7656
 7657    cx.assert_editor_state(indoc!(
 7658        r#"abc
 7659           def«ˇg»hi
 7660
 7661           jk
 7662           nlmo
 7663           "#
 7664    ));
 7665
 7666    // Change selections again
 7667    cx.set_state(indoc!(
 7668        r#"a«bc
 7669           defgˇ»hi
 7670
 7671           jk
 7672           nlmo
 7673           "#
 7674    ));
 7675
 7676    cx.update_editor(|editor, window, cx| {
 7677        editor.add_selection_below(&Default::default(), window, cx);
 7678    });
 7679
 7680    cx.assert_editor_state(indoc!(
 7681        r#"a«bcˇ»
 7682           d«efgˇ»hi
 7683
 7684           j«kˇ»
 7685           nlmo
 7686           "#
 7687    ));
 7688
 7689    cx.update_editor(|editor, window, cx| {
 7690        editor.add_selection_below(&Default::default(), window, cx);
 7691    });
 7692    cx.assert_editor_state(indoc!(
 7693        r#"a«bcˇ»
 7694           d«efgˇ»hi
 7695
 7696           j«kˇ»
 7697           n«lmoˇ»
 7698           "#
 7699    ));
 7700    cx.update_editor(|editor, window, cx| {
 7701        editor.add_selection_above(&Default::default(), window, cx);
 7702    });
 7703
 7704    cx.assert_editor_state(indoc!(
 7705        r#"a«bcˇ»
 7706           d«efgˇ»hi
 7707
 7708           j«kˇ»
 7709           nlmo
 7710           "#
 7711    ));
 7712
 7713    // Change selections again
 7714    cx.set_state(indoc!(
 7715        r#"abc
 7716           d«ˇefghi
 7717
 7718           jk
 7719           nlm»o
 7720           "#
 7721    ));
 7722
 7723    cx.update_editor(|editor, window, cx| {
 7724        editor.add_selection_above(&Default::default(), window, cx);
 7725    });
 7726
 7727    cx.assert_editor_state(indoc!(
 7728        r#"a«ˇbc»
 7729           d«ˇef»ghi
 7730
 7731           j«ˇk»
 7732           n«ˇlm»o
 7733           "#
 7734    ));
 7735
 7736    cx.update_editor(|editor, window, cx| {
 7737        editor.add_selection_below(&Default::default(), window, cx);
 7738    });
 7739
 7740    cx.assert_editor_state(indoc!(
 7741        r#"abc
 7742           d«ˇef»ghi
 7743
 7744           j«ˇk»
 7745           n«ˇlm»o
 7746           "#
 7747    ));
 7748}
 7749
 7750#[gpui::test]
 7751async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 7752    init_test(cx, |_| {});
 7753    let mut cx = EditorTestContext::new(cx).await;
 7754
 7755    cx.set_state(indoc!(
 7756        r#"line onˇe
 7757           liˇne two
 7758           line three
 7759           line four"#
 7760    ));
 7761
 7762    cx.update_editor(|editor, window, cx| {
 7763        editor.add_selection_below(&Default::default(), window, cx);
 7764    });
 7765
 7766    // test multiple cursors expand in the same direction
 7767    cx.assert_editor_state(indoc!(
 7768        r#"line onˇe
 7769           liˇne twˇo
 7770           liˇne three
 7771           line four"#
 7772    ));
 7773
 7774    cx.update_editor(|editor, window, cx| {
 7775        editor.add_selection_below(&Default::default(), window, cx);
 7776    });
 7777
 7778    cx.update_editor(|editor, window, cx| {
 7779        editor.add_selection_below(&Default::default(), window, cx);
 7780    });
 7781
 7782    // test multiple cursors expand below overflow
 7783    cx.assert_editor_state(indoc!(
 7784        r#"line onˇe
 7785           liˇne twˇo
 7786           liˇne thˇree
 7787           liˇne foˇur"#
 7788    ));
 7789
 7790    cx.update_editor(|editor, window, cx| {
 7791        editor.add_selection_above(&Default::default(), window, cx);
 7792    });
 7793
 7794    // test multiple cursors retrieves back correctly
 7795    cx.assert_editor_state(indoc!(
 7796        r#"line onˇe
 7797           liˇne twˇo
 7798           liˇne thˇree
 7799           line four"#
 7800    ));
 7801
 7802    cx.update_editor(|editor, window, cx| {
 7803        editor.add_selection_above(&Default::default(), window, cx);
 7804    });
 7805
 7806    cx.update_editor(|editor, window, cx| {
 7807        editor.add_selection_above(&Default::default(), window, cx);
 7808    });
 7809
 7810    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 7811    cx.assert_editor_state(indoc!(
 7812        r#"liˇne onˇe
 7813           liˇne two
 7814           line three
 7815           line four"#
 7816    ));
 7817
 7818    cx.update_editor(|editor, window, cx| {
 7819        editor.undo_selection(&Default::default(), window, cx);
 7820    });
 7821
 7822    // test undo
 7823    cx.assert_editor_state(indoc!(
 7824        r#"line onˇe
 7825           liˇne twˇo
 7826           line three
 7827           line four"#
 7828    ));
 7829
 7830    cx.update_editor(|editor, window, cx| {
 7831        editor.redo_selection(&Default::default(), window, cx);
 7832    });
 7833
 7834    // test redo
 7835    cx.assert_editor_state(indoc!(
 7836        r#"liˇne onˇe
 7837           liˇne two
 7838           line three
 7839           line four"#
 7840    ));
 7841
 7842    cx.set_state(indoc!(
 7843        r#"abcd
 7844           ef«ghˇ»
 7845           ijkl
 7846           «mˇ»nop"#
 7847    ));
 7848
 7849    cx.update_editor(|editor, window, cx| {
 7850        editor.add_selection_above(&Default::default(), window, cx);
 7851    });
 7852
 7853    // test multiple selections expand in the same direction
 7854    cx.assert_editor_state(indoc!(
 7855        r#"ab«cdˇ»
 7856           ef«ghˇ»
 7857           «iˇ»jkl
 7858           «mˇ»nop"#
 7859    ));
 7860
 7861    cx.update_editor(|editor, window, cx| {
 7862        editor.add_selection_above(&Default::default(), window, cx);
 7863    });
 7864
 7865    // test multiple selection upward overflow
 7866    cx.assert_editor_state(indoc!(
 7867        r#"ab«cdˇ»
 7868           «eˇ»f«ghˇ»
 7869           «iˇ»jkl
 7870           «mˇ»nop"#
 7871    ));
 7872
 7873    cx.update_editor(|editor, window, cx| {
 7874        editor.add_selection_below(&Default::default(), window, cx);
 7875    });
 7876
 7877    // test multiple selection retrieves back correctly
 7878    cx.assert_editor_state(indoc!(
 7879        r#"abcd
 7880           ef«ghˇ»
 7881           «iˇ»jkl
 7882           «mˇ»nop"#
 7883    ));
 7884
 7885    cx.update_editor(|editor, window, cx| {
 7886        editor.add_selection_below(&Default::default(), window, cx);
 7887    });
 7888
 7889    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 7890    cx.assert_editor_state(indoc!(
 7891        r#"abcd
 7892           ef«ghˇ»
 7893           ij«klˇ»
 7894           «mˇ»nop"#
 7895    ));
 7896
 7897    cx.update_editor(|editor, window, cx| {
 7898        editor.undo_selection(&Default::default(), window, cx);
 7899    });
 7900
 7901    // test undo
 7902    cx.assert_editor_state(indoc!(
 7903        r#"abcd
 7904           ef«ghˇ»
 7905           «iˇ»jkl
 7906           «mˇ»nop"#
 7907    ));
 7908
 7909    cx.update_editor(|editor, window, cx| {
 7910        editor.redo_selection(&Default::default(), window, cx);
 7911    });
 7912
 7913    // test redo
 7914    cx.assert_editor_state(indoc!(
 7915        r#"abcd
 7916           ef«ghˇ»
 7917           ij«klˇ»
 7918           «mˇ»nop"#
 7919    ));
 7920}
 7921
 7922#[gpui::test]
 7923async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 7924    init_test(cx, |_| {});
 7925    let mut cx = EditorTestContext::new(cx).await;
 7926
 7927    cx.set_state(indoc!(
 7928        r#"line onˇe
 7929           liˇne two
 7930           line three
 7931           line four"#
 7932    ));
 7933
 7934    cx.update_editor(|editor, window, cx| {
 7935        editor.add_selection_below(&Default::default(), window, cx);
 7936        editor.add_selection_below(&Default::default(), window, cx);
 7937        editor.add_selection_below(&Default::default(), window, cx);
 7938    });
 7939
 7940    // initial state with two multi cursor groups
 7941    cx.assert_editor_state(indoc!(
 7942        r#"line onˇe
 7943           liˇne twˇo
 7944           liˇne thˇree
 7945           liˇne foˇur"#
 7946    ));
 7947
 7948    // add single cursor in middle - simulate opt click
 7949    cx.update_editor(|editor, window, cx| {
 7950        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 7951        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7952        editor.end_selection(window, cx);
 7953    });
 7954
 7955    cx.assert_editor_state(indoc!(
 7956        r#"line onˇe
 7957           liˇne twˇo
 7958           liˇneˇ thˇree
 7959           liˇne foˇur"#
 7960    ));
 7961
 7962    cx.update_editor(|editor, window, cx| {
 7963        editor.add_selection_above(&Default::default(), window, cx);
 7964    });
 7965
 7966    // test new added selection expands above and existing selection shrinks
 7967    cx.assert_editor_state(indoc!(
 7968        r#"line onˇe
 7969           liˇneˇ twˇo
 7970           liˇneˇ thˇree
 7971           line four"#
 7972    ));
 7973
 7974    cx.update_editor(|editor, window, cx| {
 7975        editor.add_selection_above(&Default::default(), window, cx);
 7976    });
 7977
 7978    // test new added selection expands above and existing selection shrinks
 7979    cx.assert_editor_state(indoc!(
 7980        r#"lineˇ onˇe
 7981           liˇneˇ twˇo
 7982           lineˇ three
 7983           line four"#
 7984    ));
 7985
 7986    // intial state with two selection groups
 7987    cx.set_state(indoc!(
 7988        r#"abcd
 7989           ef«ghˇ»
 7990           ijkl
 7991           «mˇ»nop"#
 7992    ));
 7993
 7994    cx.update_editor(|editor, window, cx| {
 7995        editor.add_selection_above(&Default::default(), window, cx);
 7996        editor.add_selection_above(&Default::default(), window, cx);
 7997    });
 7998
 7999    cx.assert_editor_state(indoc!(
 8000        r#"ab«cdˇ»
 8001           «eˇ»f«ghˇ»
 8002           «iˇ»jkl
 8003           «mˇ»nop"#
 8004    ));
 8005
 8006    // add single selection in middle - simulate opt drag
 8007    cx.update_editor(|editor, window, cx| {
 8008        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8009        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8010        editor.update_selection(
 8011            DisplayPoint::new(DisplayRow(2), 4),
 8012            0,
 8013            gpui::Point::<f32>::default(),
 8014            window,
 8015            cx,
 8016        );
 8017        editor.end_selection(window, cx);
 8018    });
 8019
 8020    cx.assert_editor_state(indoc!(
 8021        r#"ab«cdˇ»
 8022           «eˇ»f«ghˇ»
 8023           «iˇ»jk«lˇ»
 8024           «mˇ»nop"#
 8025    ));
 8026
 8027    cx.update_editor(|editor, window, cx| {
 8028        editor.add_selection_below(&Default::default(), window, cx);
 8029    });
 8030
 8031    // test new added selection expands below, others shrinks from above
 8032    cx.assert_editor_state(indoc!(
 8033        r#"abcd
 8034           ef«ghˇ»
 8035           «iˇ»jk«lˇ»
 8036           «mˇ»no«pˇ»"#
 8037    ));
 8038}
 8039
 8040#[gpui::test]
 8041async fn test_select_next(cx: &mut TestAppContext) {
 8042    init_test(cx, |_| {});
 8043
 8044    let mut cx = EditorTestContext::new(cx).await;
 8045    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8046
 8047    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8048        .unwrap();
 8049    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8050
 8051    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8052        .unwrap();
 8053    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8054
 8055    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8056    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8057
 8058    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8059    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8060
 8061    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8062        .unwrap();
 8063    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8064
 8065    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8066        .unwrap();
 8067    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8068
 8069    // Test selection direction should be preserved
 8070    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8071
 8072    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8073        .unwrap();
 8074    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8075}
 8076
 8077#[gpui::test]
 8078async fn test_select_all_matches(cx: &mut TestAppContext) {
 8079    init_test(cx, |_| {});
 8080
 8081    let mut cx = EditorTestContext::new(cx).await;
 8082
 8083    // Test caret-only selections
 8084    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8085    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8086        .unwrap();
 8087    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8088
 8089    // Test left-to-right selections
 8090    cx.set_state("abc\n«abcˇ»\nabc");
 8091    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8092        .unwrap();
 8093    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8094
 8095    // Test right-to-left selections
 8096    cx.set_state("abc\n«ˇabc»\nabc");
 8097    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8098        .unwrap();
 8099    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8100
 8101    // Test selecting whitespace with caret selection
 8102    cx.set_state("abc\nˇ   abc\nabc");
 8103    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8104        .unwrap();
 8105    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8106
 8107    // Test selecting whitespace with left-to-right selection
 8108    cx.set_state("abc\n«ˇ  »abc\nabc");
 8109    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8110        .unwrap();
 8111    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8112
 8113    // Test no matches with right-to-left selection
 8114    cx.set_state("abc\n«  ˇ»abc\nabc");
 8115    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8116        .unwrap();
 8117    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8118
 8119    // Test with a single word and clip_at_line_ends=true (#29823)
 8120    cx.set_state("aˇbc");
 8121    cx.update_editor(|e, window, cx| {
 8122        e.set_clip_at_line_ends(true, cx);
 8123        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8124        e.set_clip_at_line_ends(false, cx);
 8125    });
 8126    cx.assert_editor_state("«abcˇ»");
 8127}
 8128
 8129#[gpui::test]
 8130async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8131    init_test(cx, |_| {});
 8132
 8133    let mut cx = EditorTestContext::new(cx).await;
 8134
 8135    let large_body_1 = "\nd".repeat(200);
 8136    let large_body_2 = "\ne".repeat(200);
 8137
 8138    cx.set_state(&format!(
 8139        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8140    ));
 8141    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8142        let scroll_position = editor.scroll_position(cx);
 8143        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8144        scroll_position
 8145    });
 8146
 8147    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8148        .unwrap();
 8149    cx.assert_editor_state(&format!(
 8150        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8151    ));
 8152    let scroll_position_after_selection =
 8153        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8154    assert_eq!(
 8155        initial_scroll_position, scroll_position_after_selection,
 8156        "Scroll position should not change after selecting all matches"
 8157    );
 8158}
 8159
 8160#[gpui::test]
 8161async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8162    init_test(cx, |_| {});
 8163
 8164    let mut cx = EditorLspTestContext::new_rust(
 8165        lsp::ServerCapabilities {
 8166            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8167            ..Default::default()
 8168        },
 8169        cx,
 8170    )
 8171    .await;
 8172
 8173    cx.set_state(indoc! {"
 8174        line 1
 8175        line 2
 8176        linˇe 3
 8177        line 4
 8178        line 5
 8179    "});
 8180
 8181    // Make an edit
 8182    cx.update_editor(|editor, window, cx| {
 8183        editor.handle_input("X", window, cx);
 8184    });
 8185
 8186    // Move cursor to a different position
 8187    cx.update_editor(|editor, window, cx| {
 8188        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8189            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8190        });
 8191    });
 8192
 8193    cx.assert_editor_state(indoc! {"
 8194        line 1
 8195        line 2
 8196        linXe 3
 8197        line 4
 8198        liˇne 5
 8199    "});
 8200
 8201    cx.lsp
 8202        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8203            Ok(Some(vec![lsp::TextEdit::new(
 8204                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8205                "PREFIX ".to_string(),
 8206            )]))
 8207        });
 8208
 8209    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8210        .unwrap()
 8211        .await
 8212        .unwrap();
 8213
 8214    cx.assert_editor_state(indoc! {"
 8215        PREFIX line 1
 8216        line 2
 8217        linXe 3
 8218        line 4
 8219        liˇne 5
 8220    "});
 8221
 8222    // Undo formatting
 8223    cx.update_editor(|editor, window, cx| {
 8224        editor.undo(&Default::default(), window, cx);
 8225    });
 8226
 8227    // Verify cursor moved back to position after edit
 8228    cx.assert_editor_state(indoc! {"
 8229        line 1
 8230        line 2
 8231        linXˇe 3
 8232        line 4
 8233        line 5
 8234    "});
 8235}
 8236
 8237#[gpui::test]
 8238async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8239    init_test(cx, |_| {});
 8240
 8241    let mut cx = EditorTestContext::new(cx).await;
 8242
 8243    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8244    cx.update_editor(|editor, window, cx| {
 8245        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8246    });
 8247
 8248    cx.set_state(indoc! {"
 8249        line 1
 8250        line 2
 8251        linˇe 3
 8252        line 4
 8253        line 5
 8254        line 6
 8255        line 7
 8256        line 8
 8257        line 9
 8258        line 10
 8259    "});
 8260
 8261    let snapshot = cx.buffer_snapshot();
 8262    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8263
 8264    cx.update(|_, cx| {
 8265        provider.update(cx, |provider, _| {
 8266            provider.set_edit_prediction(Some(edit_prediction::EditPrediction {
 8267                id: None,
 8268                edits: vec![(edit_position..edit_position, "X".into())],
 8269                edit_preview: None,
 8270            }))
 8271        })
 8272    });
 8273
 8274    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8275    cx.update_editor(|editor, window, cx| {
 8276        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8277    });
 8278
 8279    cx.assert_editor_state(indoc! {"
 8280        line 1
 8281        line 2
 8282        lineXˇ 3
 8283        line 4
 8284        line 5
 8285        line 6
 8286        line 7
 8287        line 8
 8288        line 9
 8289        line 10
 8290    "});
 8291
 8292    cx.update_editor(|editor, window, cx| {
 8293        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8294            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8295        });
 8296    });
 8297
 8298    cx.assert_editor_state(indoc! {"
 8299        line 1
 8300        line 2
 8301        lineX 3
 8302        line 4
 8303        line 5
 8304        line 6
 8305        line 7
 8306        line 8
 8307        line 9
 8308        liˇne 10
 8309    "});
 8310
 8311    cx.update_editor(|editor, window, cx| {
 8312        editor.undo(&Default::default(), window, cx);
 8313    });
 8314
 8315    cx.assert_editor_state(indoc! {"
 8316        line 1
 8317        line 2
 8318        lineˇ 3
 8319        line 4
 8320        line 5
 8321        line 6
 8322        line 7
 8323        line 8
 8324        line 9
 8325        line 10
 8326    "});
 8327}
 8328
 8329#[gpui::test]
 8330async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8331    init_test(cx, |_| {});
 8332
 8333    let mut cx = EditorTestContext::new(cx).await;
 8334    cx.set_state(
 8335        r#"let foo = 2;
 8336lˇet foo = 2;
 8337let fooˇ = 2;
 8338let foo = 2;
 8339let foo = ˇ2;"#,
 8340    );
 8341
 8342    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8343        .unwrap();
 8344    cx.assert_editor_state(
 8345        r#"let foo = 2;
 8346«letˇ» foo = 2;
 8347let «fooˇ» = 2;
 8348let foo = 2;
 8349let foo = «2ˇ»;"#,
 8350    );
 8351
 8352    // noop for multiple selections with different contents
 8353    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8354        .unwrap();
 8355    cx.assert_editor_state(
 8356        r#"let foo = 2;
 8357«letˇ» foo = 2;
 8358let «fooˇ» = 2;
 8359let foo = 2;
 8360let foo = «2ˇ»;"#,
 8361    );
 8362
 8363    // Test last selection direction should be preserved
 8364    cx.set_state(
 8365        r#"let foo = 2;
 8366let foo = 2;
 8367let «fooˇ» = 2;
 8368let «ˇfoo» = 2;
 8369let foo = 2;"#,
 8370    );
 8371
 8372    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8373        .unwrap();
 8374    cx.assert_editor_state(
 8375        r#"let foo = 2;
 8376let foo = 2;
 8377let «fooˇ» = 2;
 8378let «ˇfoo» = 2;
 8379let «ˇfoo» = 2;"#,
 8380    );
 8381}
 8382
 8383#[gpui::test]
 8384async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8385    init_test(cx, |_| {});
 8386
 8387    let mut cx =
 8388        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8389
 8390    cx.assert_editor_state(indoc! {"
 8391        ˇbbb
 8392        ccc
 8393
 8394        bbb
 8395        ccc
 8396        "});
 8397    cx.dispatch_action(SelectPrevious::default());
 8398    cx.assert_editor_state(indoc! {"
 8399                «bbbˇ»
 8400                ccc
 8401
 8402                bbb
 8403                ccc
 8404                "});
 8405    cx.dispatch_action(SelectPrevious::default());
 8406    cx.assert_editor_state(indoc! {"
 8407                «bbbˇ»
 8408                ccc
 8409
 8410                «bbbˇ»
 8411                ccc
 8412                "});
 8413}
 8414
 8415#[gpui::test]
 8416async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8417    init_test(cx, |_| {});
 8418
 8419    let mut cx = EditorTestContext::new(cx).await;
 8420    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8421
 8422    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8423        .unwrap();
 8424    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8425
 8426    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8427        .unwrap();
 8428    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8429
 8430    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8431    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8432
 8433    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8434    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8435
 8436    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8437        .unwrap();
 8438    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8439
 8440    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8441        .unwrap();
 8442    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8443}
 8444
 8445#[gpui::test]
 8446async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8447    init_test(cx, |_| {});
 8448
 8449    let mut cx = EditorTestContext::new(cx).await;
 8450    cx.set_state("");
 8451
 8452    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8453        .unwrap();
 8454    cx.assert_editor_state("«aˇ»");
 8455    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8456        .unwrap();
 8457    cx.assert_editor_state("«aˇ»");
 8458}
 8459
 8460#[gpui::test]
 8461async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8462    init_test(cx, |_| {});
 8463
 8464    let mut cx = EditorTestContext::new(cx).await;
 8465    cx.set_state(
 8466        r#"let foo = 2;
 8467lˇet foo = 2;
 8468let fooˇ = 2;
 8469let foo = 2;
 8470let foo = ˇ2;"#,
 8471    );
 8472
 8473    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8474        .unwrap();
 8475    cx.assert_editor_state(
 8476        r#"let foo = 2;
 8477«letˇ» foo = 2;
 8478let «fooˇ» = 2;
 8479let foo = 2;
 8480let foo = «2ˇ»;"#,
 8481    );
 8482
 8483    // noop for multiple selections with different contents
 8484    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8485        .unwrap();
 8486    cx.assert_editor_state(
 8487        r#"let foo = 2;
 8488«letˇ» foo = 2;
 8489let «fooˇ» = 2;
 8490let foo = 2;
 8491let foo = «2ˇ»;"#,
 8492    );
 8493}
 8494
 8495#[gpui::test]
 8496async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8497    init_test(cx, |_| {});
 8498
 8499    let mut cx = EditorTestContext::new(cx).await;
 8500    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8501
 8502    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8503        .unwrap();
 8504    // selection direction is preserved
 8505    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8506
 8507    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8508        .unwrap();
 8509    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8510
 8511    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8512    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8513
 8514    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8515    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8516
 8517    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8518        .unwrap();
 8519    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8520
 8521    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8522        .unwrap();
 8523    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8524}
 8525
 8526#[gpui::test]
 8527async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8528    init_test(cx, |_| {});
 8529
 8530    let language = Arc::new(Language::new(
 8531        LanguageConfig::default(),
 8532        Some(tree_sitter_rust::LANGUAGE.into()),
 8533    ));
 8534
 8535    let text = r#"
 8536        use mod1::mod2::{mod3, mod4};
 8537
 8538        fn fn_1(param1: bool, param2: &str) {
 8539            let var1 = "text";
 8540        }
 8541    "#
 8542    .unindent();
 8543
 8544    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8545    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8546    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8547
 8548    editor
 8549        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8550        .await;
 8551
 8552    editor.update_in(cx, |editor, window, cx| {
 8553        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8554            s.select_display_ranges([
 8555                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8556                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8557                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8558            ]);
 8559        });
 8560        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8561    });
 8562    editor.update(cx, |editor, cx| {
 8563        assert_text_with_selections(
 8564            editor,
 8565            indoc! {r#"
 8566                use mod1::mod2::{mod3, «mod4ˇ»};
 8567
 8568                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8569                    let var1 = "«ˇtext»";
 8570                }
 8571            "#},
 8572            cx,
 8573        );
 8574    });
 8575
 8576    editor.update_in(cx, |editor, window, cx| {
 8577        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8578    });
 8579    editor.update(cx, |editor, cx| {
 8580        assert_text_with_selections(
 8581            editor,
 8582            indoc! {r#"
 8583                use mod1::mod2::«{mod3, mod4}ˇ»;
 8584
 8585                «ˇfn fn_1(param1: bool, param2: &str) {
 8586                    let var1 = "text";
 8587 8588            "#},
 8589            cx,
 8590        );
 8591    });
 8592
 8593    editor.update_in(cx, |editor, window, cx| {
 8594        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8595    });
 8596    assert_eq!(
 8597        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8598        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8599    );
 8600
 8601    // Trying to expand the selected syntax node one more time has no effect.
 8602    editor.update_in(cx, |editor, window, cx| {
 8603        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8604    });
 8605    assert_eq!(
 8606        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8607        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8608    );
 8609
 8610    editor.update_in(cx, |editor, window, cx| {
 8611        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8612    });
 8613    editor.update(cx, |editor, cx| {
 8614        assert_text_with_selections(
 8615            editor,
 8616            indoc! {r#"
 8617                use mod1::mod2::«{mod3, mod4}ˇ»;
 8618
 8619                «ˇfn fn_1(param1: bool, param2: &str) {
 8620                    let var1 = "text";
 8621 8622            "#},
 8623            cx,
 8624        );
 8625    });
 8626
 8627    editor.update_in(cx, |editor, window, cx| {
 8628        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8629    });
 8630    editor.update(cx, |editor, cx| {
 8631        assert_text_with_selections(
 8632            editor,
 8633            indoc! {r#"
 8634                use mod1::mod2::{mod3, «mod4ˇ»};
 8635
 8636                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8637                    let var1 = "«ˇtext»";
 8638                }
 8639            "#},
 8640            cx,
 8641        );
 8642    });
 8643
 8644    editor.update_in(cx, |editor, window, cx| {
 8645        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8646    });
 8647    editor.update(cx, |editor, cx| {
 8648        assert_text_with_selections(
 8649            editor,
 8650            indoc! {r#"
 8651                use mod1::mod2::{mod3, moˇd4};
 8652
 8653                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8654                    let var1 = "teˇxt";
 8655                }
 8656            "#},
 8657            cx,
 8658        );
 8659    });
 8660
 8661    // Trying to shrink the selected syntax node one more time has no effect.
 8662    editor.update_in(cx, |editor, window, cx| {
 8663        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8664    });
 8665    editor.update_in(cx, |editor, _, cx| {
 8666        assert_text_with_selections(
 8667            editor,
 8668            indoc! {r#"
 8669                use mod1::mod2::{mod3, moˇd4};
 8670
 8671                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8672                    let var1 = "teˇxt";
 8673                }
 8674            "#},
 8675            cx,
 8676        );
 8677    });
 8678
 8679    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8680    // a fold.
 8681    editor.update_in(cx, |editor, window, cx| {
 8682        editor.fold_creases(
 8683            vec![
 8684                Crease::simple(
 8685                    Point::new(0, 21)..Point::new(0, 24),
 8686                    FoldPlaceholder::test(),
 8687                ),
 8688                Crease::simple(
 8689                    Point::new(3, 20)..Point::new(3, 22),
 8690                    FoldPlaceholder::test(),
 8691                ),
 8692            ],
 8693            true,
 8694            window,
 8695            cx,
 8696        );
 8697        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8698    });
 8699    editor.update(cx, |editor, cx| {
 8700        assert_text_with_selections(
 8701            editor,
 8702            indoc! {r#"
 8703                use mod1::mod2::«{mod3, mod4}ˇ»;
 8704
 8705                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8706                    let var1 = "«ˇtext»";
 8707                }
 8708            "#},
 8709            cx,
 8710        );
 8711    });
 8712}
 8713
 8714#[gpui::test]
 8715async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8716    init_test(cx, |_| {});
 8717
 8718    let language = Arc::new(Language::new(
 8719        LanguageConfig::default(),
 8720        Some(tree_sitter_rust::LANGUAGE.into()),
 8721    ));
 8722
 8723    let text = "let a = 2;";
 8724
 8725    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8726    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8727    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8728
 8729    editor
 8730        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8731        .await;
 8732
 8733    // Test case 1: Cursor at end of word
 8734    editor.update_in(cx, |editor, window, cx| {
 8735        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8736            s.select_display_ranges([
 8737                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 8738            ]);
 8739        });
 8740    });
 8741    editor.update(cx, |editor, cx| {
 8742        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 8743    });
 8744    editor.update_in(cx, |editor, window, cx| {
 8745        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8746    });
 8747    editor.update(cx, |editor, cx| {
 8748        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 8749    });
 8750    editor.update_in(cx, |editor, window, cx| {
 8751        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8752    });
 8753    editor.update(cx, |editor, cx| {
 8754        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8755    });
 8756
 8757    // Test case 2: Cursor at end of statement
 8758    editor.update_in(cx, |editor, window, cx| {
 8759        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8760            s.select_display_ranges([
 8761                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 8762            ]);
 8763        });
 8764    });
 8765    editor.update(cx, |editor, cx| {
 8766        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 8767    });
 8768    editor.update_in(cx, |editor, window, cx| {
 8769        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8770    });
 8771    editor.update(cx, |editor, cx| {
 8772        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8773    });
 8774}
 8775
 8776#[gpui::test]
 8777async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 8778    init_test(cx, |_| {});
 8779
 8780    let language = Arc::new(Language::new(
 8781        LanguageConfig {
 8782            name: "JavaScript".into(),
 8783            ..Default::default()
 8784        },
 8785        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8786    ));
 8787
 8788    let text = r#"
 8789        let a = {
 8790            key: "value",
 8791        };
 8792    "#
 8793    .unindent();
 8794
 8795    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8796    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8797    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8798
 8799    editor
 8800        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8801        .await;
 8802
 8803    // Test case 1: Cursor after '{'
 8804    editor.update_in(cx, |editor, window, cx| {
 8805        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8806            s.select_display_ranges([
 8807                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 8808            ]);
 8809        });
 8810    });
 8811    editor.update(cx, |editor, cx| {
 8812        assert_text_with_selections(
 8813            editor,
 8814            indoc! {r#"
 8815                let a = {ˇ
 8816                    key: "value",
 8817                };
 8818            "#},
 8819            cx,
 8820        );
 8821    });
 8822    editor.update_in(cx, |editor, window, cx| {
 8823        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8824    });
 8825    editor.update(cx, |editor, cx| {
 8826        assert_text_with_selections(
 8827            editor,
 8828            indoc! {r#"
 8829                let a = «ˇ{
 8830                    key: "value",
 8831                }»;
 8832            "#},
 8833            cx,
 8834        );
 8835    });
 8836
 8837    // Test case 2: Cursor after ':'
 8838    editor.update_in(cx, |editor, window, cx| {
 8839        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8840            s.select_display_ranges([
 8841                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 8842            ]);
 8843        });
 8844    });
 8845    editor.update(cx, |editor, cx| {
 8846        assert_text_with_selections(
 8847            editor,
 8848            indoc! {r#"
 8849                let a = {
 8850                    key:ˇ "value",
 8851                };
 8852            "#},
 8853            cx,
 8854        );
 8855    });
 8856    editor.update_in(cx, |editor, window, cx| {
 8857        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8858    });
 8859    editor.update(cx, |editor, cx| {
 8860        assert_text_with_selections(
 8861            editor,
 8862            indoc! {r#"
 8863                let a = {
 8864                    «ˇkey: "value"»,
 8865                };
 8866            "#},
 8867            cx,
 8868        );
 8869    });
 8870    editor.update_in(cx, |editor, window, cx| {
 8871        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8872    });
 8873    editor.update(cx, |editor, cx| {
 8874        assert_text_with_selections(
 8875            editor,
 8876            indoc! {r#"
 8877                let a = «ˇ{
 8878                    key: "value",
 8879                }»;
 8880            "#},
 8881            cx,
 8882        );
 8883    });
 8884
 8885    // Test case 3: Cursor after ','
 8886    editor.update_in(cx, |editor, window, cx| {
 8887        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8888            s.select_display_ranges([
 8889                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 8890            ]);
 8891        });
 8892    });
 8893    editor.update(cx, |editor, cx| {
 8894        assert_text_with_selections(
 8895            editor,
 8896            indoc! {r#"
 8897                let a = {
 8898                    key: "value",ˇ
 8899                };
 8900            "#},
 8901            cx,
 8902        );
 8903    });
 8904    editor.update_in(cx, |editor, window, cx| {
 8905        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8906    });
 8907    editor.update(cx, |editor, cx| {
 8908        assert_text_with_selections(
 8909            editor,
 8910            indoc! {r#"
 8911                let a = «ˇ{
 8912                    key: "value",
 8913                }»;
 8914            "#},
 8915            cx,
 8916        );
 8917    });
 8918
 8919    // Test case 4: Cursor after ';'
 8920    editor.update_in(cx, |editor, window, cx| {
 8921        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8922            s.select_display_ranges([
 8923                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 8924            ]);
 8925        });
 8926    });
 8927    editor.update(cx, |editor, cx| {
 8928        assert_text_with_selections(
 8929            editor,
 8930            indoc! {r#"
 8931                let a = {
 8932                    key: "value",
 8933                };ˇ
 8934            "#},
 8935            cx,
 8936        );
 8937    });
 8938    editor.update_in(cx, |editor, window, cx| {
 8939        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8940    });
 8941    editor.update(cx, |editor, cx| {
 8942        assert_text_with_selections(
 8943            editor,
 8944            indoc! {r#"
 8945                «ˇlet a = {
 8946                    key: "value",
 8947                };
 8948                »"#},
 8949            cx,
 8950        );
 8951    });
 8952}
 8953
 8954#[gpui::test]
 8955async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 8956    init_test(cx, |_| {});
 8957
 8958    let language = Arc::new(Language::new(
 8959        LanguageConfig::default(),
 8960        Some(tree_sitter_rust::LANGUAGE.into()),
 8961    ));
 8962
 8963    let text = r#"
 8964        use mod1::mod2::{mod3, mod4};
 8965
 8966        fn fn_1(param1: bool, param2: &str) {
 8967            let var1 = "hello world";
 8968        }
 8969    "#
 8970    .unindent();
 8971
 8972    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8973    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8974    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8975
 8976    editor
 8977        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8978        .await;
 8979
 8980    // Test 1: Cursor on a letter of a string word
 8981    editor.update_in(cx, |editor, window, cx| {
 8982        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8983            s.select_display_ranges([
 8984                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 8985            ]);
 8986        });
 8987    });
 8988    editor.update_in(cx, |editor, window, cx| {
 8989        assert_text_with_selections(
 8990            editor,
 8991            indoc! {r#"
 8992                use mod1::mod2::{mod3, mod4};
 8993
 8994                fn fn_1(param1: bool, param2: &str) {
 8995                    let var1 = "hˇello world";
 8996                }
 8997            "#},
 8998            cx,
 8999        );
 9000        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9001        assert_text_with_selections(
 9002            editor,
 9003            indoc! {r#"
 9004                use mod1::mod2::{mod3, mod4};
 9005
 9006                fn fn_1(param1: bool, param2: &str) {
 9007                    let var1 = "«ˇhello» world";
 9008                }
 9009            "#},
 9010            cx,
 9011        );
 9012    });
 9013
 9014    // Test 2: Partial selection within a word
 9015    editor.update_in(cx, |editor, window, cx| {
 9016        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9017            s.select_display_ranges([
 9018                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9019            ]);
 9020        });
 9021    });
 9022    editor.update_in(cx, |editor, window, cx| {
 9023        assert_text_with_selections(
 9024            editor,
 9025            indoc! {r#"
 9026                use mod1::mod2::{mod3, mod4};
 9027
 9028                fn fn_1(param1: bool, param2: &str) {
 9029                    let var1 = "h«elˇ»lo world";
 9030                }
 9031            "#},
 9032            cx,
 9033        );
 9034        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9035        assert_text_with_selections(
 9036            editor,
 9037            indoc! {r#"
 9038                use mod1::mod2::{mod3, mod4};
 9039
 9040                fn fn_1(param1: bool, param2: &str) {
 9041                    let var1 = "«ˇhello» world";
 9042                }
 9043            "#},
 9044            cx,
 9045        );
 9046    });
 9047
 9048    // Test 3: Complete word already selected
 9049    editor.update_in(cx, |editor, window, cx| {
 9050        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9051            s.select_display_ranges([
 9052                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9053            ]);
 9054        });
 9055    });
 9056    editor.update_in(cx, |editor, window, cx| {
 9057        assert_text_with_selections(
 9058            editor,
 9059            indoc! {r#"
 9060                use mod1::mod2::{mod3, mod4};
 9061
 9062                fn fn_1(param1: bool, param2: &str) {
 9063                    let var1 = "«helloˇ» world";
 9064                }
 9065            "#},
 9066            cx,
 9067        );
 9068        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9069        assert_text_with_selections(
 9070            editor,
 9071            indoc! {r#"
 9072                use mod1::mod2::{mod3, mod4};
 9073
 9074                fn fn_1(param1: bool, param2: &str) {
 9075                    let var1 = "«hello worldˇ»";
 9076                }
 9077            "#},
 9078            cx,
 9079        );
 9080    });
 9081
 9082    // Test 4: Selection spanning across words
 9083    editor.update_in(cx, |editor, window, cx| {
 9084        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9085            s.select_display_ranges([
 9086                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9087            ]);
 9088        });
 9089    });
 9090    editor.update_in(cx, |editor, window, cx| {
 9091        assert_text_with_selections(
 9092            editor,
 9093            indoc! {r#"
 9094                use mod1::mod2::{mod3, mod4};
 9095
 9096                fn fn_1(param1: bool, param2: &str) {
 9097                    let var1 = "hel«lo woˇ»rld";
 9098                }
 9099            "#},
 9100            cx,
 9101        );
 9102        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9103        assert_text_with_selections(
 9104            editor,
 9105            indoc! {r#"
 9106                use mod1::mod2::{mod3, mod4};
 9107
 9108                fn fn_1(param1: bool, param2: &str) {
 9109                    let var1 = "«ˇhello world»";
 9110                }
 9111            "#},
 9112            cx,
 9113        );
 9114    });
 9115
 9116    // Test 5: Expansion beyond string
 9117    editor.update_in(cx, |editor, window, cx| {
 9118        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9119        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9120        assert_text_with_selections(
 9121            editor,
 9122            indoc! {r#"
 9123                use mod1::mod2::{mod3, mod4};
 9124
 9125                fn fn_1(param1: bool, param2: &str) {
 9126                    «ˇlet var1 = "hello world";»
 9127                }
 9128            "#},
 9129            cx,
 9130        );
 9131    });
 9132}
 9133
 9134#[gpui::test]
 9135async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9136    init_test(cx, |_| {});
 9137
 9138    let mut cx = EditorTestContext::new(cx).await;
 9139
 9140    let language = Arc::new(Language::new(
 9141        LanguageConfig::default(),
 9142        Some(tree_sitter_rust::LANGUAGE.into()),
 9143    ));
 9144
 9145    cx.update_buffer(|buffer, cx| {
 9146        buffer.set_language(Some(language), cx);
 9147    });
 9148
 9149    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9150    cx.update_editor(|editor, window, cx| {
 9151        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9152    });
 9153
 9154    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9155
 9156    cx.set_state(indoc! { r#"fn a() {
 9157          // what
 9158          // a
 9159          // ˇlong
 9160          // method
 9161          // I
 9162          // sure
 9163          // hope
 9164          // it
 9165          // works
 9166    }"# });
 9167
 9168    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9169    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9170    cx.update(|_, cx| {
 9171        multi_buffer.update(cx, |multi_buffer, cx| {
 9172            multi_buffer.set_excerpts_for_path(
 9173                PathKey::for_buffer(&buffer, cx),
 9174                buffer,
 9175                [Point::new(1, 0)..Point::new(1, 0)],
 9176                3,
 9177                cx,
 9178            );
 9179        });
 9180    });
 9181
 9182    let editor2 = cx.new_window_entity(|window, cx| {
 9183        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9184    });
 9185
 9186    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9187    cx.update_editor(|editor, window, cx| {
 9188        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9189            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9190        })
 9191    });
 9192
 9193    cx.assert_editor_state(indoc! { "
 9194        fn a() {
 9195              // what
 9196              // a
 9197        ˇ      // long
 9198              // method"});
 9199
 9200    cx.update_editor(|editor, window, cx| {
 9201        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9202    });
 9203
 9204    // Although we could potentially make the action work when the syntax node
 9205    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9206    // did. Maybe we could also expand the excerpt to contain the range?
 9207    cx.assert_editor_state(indoc! { "
 9208        fn a() {
 9209              // what
 9210              // a
 9211        ˇ      // long
 9212              // method"});
 9213}
 9214
 9215#[gpui::test]
 9216async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9217    init_test(cx, |_| {});
 9218
 9219    let base_text = r#"
 9220        impl A {
 9221            // this is an uncommitted comment
 9222
 9223            fn b() {
 9224                c();
 9225            }
 9226
 9227            // this is another uncommitted comment
 9228
 9229            fn d() {
 9230                // e
 9231                // f
 9232            }
 9233        }
 9234
 9235        fn g() {
 9236            // h
 9237        }
 9238    "#
 9239    .unindent();
 9240
 9241    let text = r#"
 9242        ˇimpl A {
 9243
 9244            fn b() {
 9245                c();
 9246            }
 9247
 9248            fn d() {
 9249                // e
 9250                // f
 9251            }
 9252        }
 9253
 9254        fn g() {
 9255            // h
 9256        }
 9257    "#
 9258    .unindent();
 9259
 9260    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9261    cx.set_state(&text);
 9262    cx.set_head_text(&base_text);
 9263    cx.update_editor(|editor, window, cx| {
 9264        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9265    });
 9266
 9267    cx.assert_state_with_diff(
 9268        "
 9269        ˇimpl A {
 9270      -     // this is an uncommitted comment
 9271
 9272            fn b() {
 9273                c();
 9274            }
 9275
 9276      -     // this is another uncommitted comment
 9277      -
 9278            fn d() {
 9279                // e
 9280                // f
 9281            }
 9282        }
 9283
 9284        fn g() {
 9285            // h
 9286        }
 9287    "
 9288        .unindent(),
 9289    );
 9290
 9291    let expected_display_text = "
 9292        impl A {
 9293            // this is an uncommitted comment
 9294
 9295            fn b() {
 9296 9297            }
 9298
 9299            // this is another uncommitted comment
 9300
 9301            fn d() {
 9302 9303            }
 9304        }
 9305
 9306        fn g() {
 9307 9308        }
 9309        "
 9310    .unindent();
 9311
 9312    cx.update_editor(|editor, window, cx| {
 9313        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9314        assert_eq!(editor.display_text(cx), expected_display_text);
 9315    });
 9316}
 9317
 9318#[gpui::test]
 9319async fn test_autoindent(cx: &mut TestAppContext) {
 9320    init_test(cx, |_| {});
 9321
 9322    let language = Arc::new(
 9323        Language::new(
 9324            LanguageConfig {
 9325                brackets: BracketPairConfig {
 9326                    pairs: vec![
 9327                        BracketPair {
 9328                            start: "{".to_string(),
 9329                            end: "}".to_string(),
 9330                            close: false,
 9331                            surround: false,
 9332                            newline: true,
 9333                        },
 9334                        BracketPair {
 9335                            start: "(".to_string(),
 9336                            end: ")".to_string(),
 9337                            close: false,
 9338                            surround: false,
 9339                            newline: true,
 9340                        },
 9341                    ],
 9342                    ..Default::default()
 9343                },
 9344                ..Default::default()
 9345            },
 9346            Some(tree_sitter_rust::LANGUAGE.into()),
 9347        )
 9348        .with_indents_query(
 9349            r#"
 9350                (_ "(" ")" @end) @indent
 9351                (_ "{" "}" @end) @indent
 9352            "#,
 9353        )
 9354        .unwrap(),
 9355    );
 9356
 9357    let text = "fn a() {}";
 9358
 9359    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9360    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9361    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9362    editor
 9363        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9364        .await;
 9365
 9366    editor.update_in(cx, |editor, window, cx| {
 9367        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9368            s.select_ranges([5..5, 8..8, 9..9])
 9369        });
 9370        editor.newline(&Newline, window, cx);
 9371        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9372        assert_eq!(
 9373            editor.selections.ranges(cx),
 9374            &[
 9375                Point::new(1, 4)..Point::new(1, 4),
 9376                Point::new(3, 4)..Point::new(3, 4),
 9377                Point::new(5, 0)..Point::new(5, 0)
 9378            ]
 9379        );
 9380    });
 9381}
 9382
 9383#[gpui::test]
 9384async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9385    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9386
 9387    let language = Arc::new(
 9388        Language::new(
 9389            LanguageConfig {
 9390                brackets: BracketPairConfig {
 9391                    pairs: vec![
 9392                        BracketPair {
 9393                            start: "{".to_string(),
 9394                            end: "}".to_string(),
 9395                            close: false,
 9396                            surround: false,
 9397                            newline: true,
 9398                        },
 9399                        BracketPair {
 9400                            start: "(".to_string(),
 9401                            end: ")".to_string(),
 9402                            close: false,
 9403                            surround: false,
 9404                            newline: true,
 9405                        },
 9406                    ],
 9407                    ..Default::default()
 9408                },
 9409                ..Default::default()
 9410            },
 9411            Some(tree_sitter_rust::LANGUAGE.into()),
 9412        )
 9413        .with_indents_query(
 9414            r#"
 9415                (_ "(" ")" @end) @indent
 9416                (_ "{" "}" @end) @indent
 9417            "#,
 9418        )
 9419        .unwrap(),
 9420    );
 9421
 9422    let text = "fn a() {}";
 9423
 9424    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9425    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9426    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9427    editor
 9428        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9429        .await;
 9430
 9431    editor.update_in(cx, |editor, window, cx| {
 9432        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9433            s.select_ranges([5..5, 8..8, 9..9])
 9434        });
 9435        editor.newline(&Newline, window, cx);
 9436        assert_eq!(
 9437            editor.text(cx),
 9438            indoc!(
 9439                "
 9440                fn a(
 9441
 9442                ) {
 9443
 9444                }
 9445                "
 9446            )
 9447        );
 9448        assert_eq!(
 9449            editor.selections.ranges(cx),
 9450            &[
 9451                Point::new(1, 0)..Point::new(1, 0),
 9452                Point::new(3, 0)..Point::new(3, 0),
 9453                Point::new(5, 0)..Point::new(5, 0)
 9454            ]
 9455        );
 9456    });
 9457}
 9458
 9459#[gpui::test]
 9460async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9461    init_test(cx, |settings| {
 9462        settings.defaults.auto_indent = Some(true);
 9463        settings.languages.0.insert(
 9464            "python".into(),
 9465            LanguageSettingsContent {
 9466                auto_indent: Some(false),
 9467                ..Default::default()
 9468            },
 9469        );
 9470    });
 9471
 9472    let mut cx = EditorTestContext::new(cx).await;
 9473
 9474    let injected_language = Arc::new(
 9475        Language::new(
 9476            LanguageConfig {
 9477                brackets: BracketPairConfig {
 9478                    pairs: vec![
 9479                        BracketPair {
 9480                            start: "{".to_string(),
 9481                            end: "}".to_string(),
 9482                            close: false,
 9483                            surround: false,
 9484                            newline: true,
 9485                        },
 9486                        BracketPair {
 9487                            start: "(".to_string(),
 9488                            end: ")".to_string(),
 9489                            close: true,
 9490                            surround: false,
 9491                            newline: true,
 9492                        },
 9493                    ],
 9494                    ..Default::default()
 9495                },
 9496                name: "python".into(),
 9497                ..Default::default()
 9498            },
 9499            Some(tree_sitter_python::LANGUAGE.into()),
 9500        )
 9501        .with_indents_query(
 9502            r#"
 9503                (_ "(" ")" @end) @indent
 9504                (_ "{" "}" @end) @indent
 9505            "#,
 9506        )
 9507        .unwrap(),
 9508    );
 9509
 9510    let language = Arc::new(
 9511        Language::new(
 9512            LanguageConfig {
 9513                brackets: BracketPairConfig {
 9514                    pairs: vec![
 9515                        BracketPair {
 9516                            start: "{".to_string(),
 9517                            end: "}".to_string(),
 9518                            close: false,
 9519                            surround: false,
 9520                            newline: true,
 9521                        },
 9522                        BracketPair {
 9523                            start: "(".to_string(),
 9524                            end: ")".to_string(),
 9525                            close: true,
 9526                            surround: false,
 9527                            newline: true,
 9528                        },
 9529                    ],
 9530                    ..Default::default()
 9531                },
 9532                name: LanguageName::new("rust"),
 9533                ..Default::default()
 9534            },
 9535            Some(tree_sitter_rust::LANGUAGE.into()),
 9536        )
 9537        .with_indents_query(
 9538            r#"
 9539                (_ "(" ")" @end) @indent
 9540                (_ "{" "}" @end) @indent
 9541            "#,
 9542        )
 9543        .unwrap()
 9544        .with_injection_query(
 9545            r#"
 9546            (macro_invocation
 9547                macro: (identifier) @_macro_name
 9548                (token_tree) @injection.content
 9549                (#set! injection.language "python"))
 9550           "#,
 9551        )
 9552        .unwrap(),
 9553    );
 9554
 9555    cx.language_registry().add(injected_language);
 9556    cx.language_registry().add(language.clone());
 9557
 9558    cx.update_buffer(|buffer, cx| {
 9559        buffer.set_language(Some(language), cx);
 9560    });
 9561
 9562    cx.set_state(r#"struct A {ˇ}"#);
 9563
 9564    cx.update_editor(|editor, window, cx| {
 9565        editor.newline(&Default::default(), window, cx);
 9566    });
 9567
 9568    cx.assert_editor_state(indoc!(
 9569        "struct A {
 9570            ˇ
 9571        }"
 9572    ));
 9573
 9574    cx.set_state(r#"select_biased!(ˇ)"#);
 9575
 9576    cx.update_editor(|editor, window, cx| {
 9577        editor.newline(&Default::default(), window, cx);
 9578        editor.handle_input("def ", window, cx);
 9579        editor.handle_input("(", window, cx);
 9580        editor.newline(&Default::default(), window, cx);
 9581        editor.handle_input("a", window, cx);
 9582    });
 9583
 9584    cx.assert_editor_state(indoc!(
 9585        "select_biased!(
 9586        def (
 9587 9588        )
 9589        )"
 9590    ));
 9591}
 9592
 9593#[gpui::test]
 9594async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9595    init_test(cx, |_| {});
 9596
 9597    {
 9598        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9599        cx.set_state(indoc! {"
 9600            impl A {
 9601
 9602                fn b() {}
 9603
 9604            «fn c() {
 9605
 9606            }ˇ»
 9607            }
 9608        "});
 9609
 9610        cx.update_editor(|editor, window, cx| {
 9611            editor.autoindent(&Default::default(), window, cx);
 9612        });
 9613
 9614        cx.assert_editor_state(indoc! {"
 9615            impl A {
 9616
 9617                fn b() {}
 9618
 9619                «fn c() {
 9620
 9621                }ˇ»
 9622            }
 9623        "});
 9624    }
 9625
 9626    {
 9627        let mut cx = EditorTestContext::new_multibuffer(
 9628            cx,
 9629            [indoc! { "
 9630                impl A {
 9631                «
 9632                // a
 9633                fn b(){}
 9634                »
 9635                «
 9636                    }
 9637                    fn c(){}
 9638                »
 9639            "}],
 9640        );
 9641
 9642        let buffer = cx.update_editor(|editor, _, cx| {
 9643            let buffer = editor.buffer().update(cx, |buffer, _| {
 9644                buffer.all_buffers().iter().next().unwrap().clone()
 9645            });
 9646            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9647            buffer
 9648        });
 9649
 9650        cx.run_until_parked();
 9651        cx.update_editor(|editor, window, cx| {
 9652            editor.select_all(&Default::default(), window, cx);
 9653            editor.autoindent(&Default::default(), window, cx)
 9654        });
 9655        cx.run_until_parked();
 9656
 9657        cx.update(|_, cx| {
 9658            assert_eq!(
 9659                buffer.read(cx).text(),
 9660                indoc! { "
 9661                    impl A {
 9662
 9663                        // a
 9664                        fn b(){}
 9665
 9666
 9667                    }
 9668                    fn c(){}
 9669
 9670                " }
 9671            )
 9672        });
 9673    }
 9674}
 9675
 9676#[gpui::test]
 9677async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9678    init_test(cx, |_| {});
 9679
 9680    let mut cx = EditorTestContext::new(cx).await;
 9681
 9682    let language = Arc::new(Language::new(
 9683        LanguageConfig {
 9684            brackets: BracketPairConfig {
 9685                pairs: vec![
 9686                    BracketPair {
 9687                        start: "{".to_string(),
 9688                        end: "}".to_string(),
 9689                        close: true,
 9690                        surround: true,
 9691                        newline: true,
 9692                    },
 9693                    BracketPair {
 9694                        start: "(".to_string(),
 9695                        end: ")".to_string(),
 9696                        close: true,
 9697                        surround: true,
 9698                        newline: true,
 9699                    },
 9700                    BracketPair {
 9701                        start: "/*".to_string(),
 9702                        end: " */".to_string(),
 9703                        close: true,
 9704                        surround: true,
 9705                        newline: true,
 9706                    },
 9707                    BracketPair {
 9708                        start: "[".to_string(),
 9709                        end: "]".to_string(),
 9710                        close: false,
 9711                        surround: false,
 9712                        newline: true,
 9713                    },
 9714                    BracketPair {
 9715                        start: "\"".to_string(),
 9716                        end: "\"".to_string(),
 9717                        close: true,
 9718                        surround: true,
 9719                        newline: false,
 9720                    },
 9721                    BracketPair {
 9722                        start: "<".to_string(),
 9723                        end: ">".to_string(),
 9724                        close: false,
 9725                        surround: true,
 9726                        newline: true,
 9727                    },
 9728                ],
 9729                ..Default::default()
 9730            },
 9731            autoclose_before: "})]".to_string(),
 9732            ..Default::default()
 9733        },
 9734        Some(tree_sitter_rust::LANGUAGE.into()),
 9735    ));
 9736
 9737    cx.language_registry().add(language.clone());
 9738    cx.update_buffer(|buffer, cx| {
 9739        buffer.set_language(Some(language), cx);
 9740    });
 9741
 9742    cx.set_state(
 9743        &r#"
 9744            🏀ˇ
 9745            εˇ
 9746            ❤️ˇ
 9747        "#
 9748        .unindent(),
 9749    );
 9750
 9751    // autoclose multiple nested brackets at multiple cursors
 9752    cx.update_editor(|editor, window, cx| {
 9753        editor.handle_input("{", window, cx);
 9754        editor.handle_input("{", window, cx);
 9755        editor.handle_input("{", window, cx);
 9756    });
 9757    cx.assert_editor_state(
 9758        &"
 9759            🏀{{{ˇ}}}
 9760            ε{{{ˇ}}}
 9761            ❤️{{{ˇ}}}
 9762        "
 9763        .unindent(),
 9764    );
 9765
 9766    // insert a different closing bracket
 9767    cx.update_editor(|editor, window, cx| {
 9768        editor.handle_input(")", window, cx);
 9769    });
 9770    cx.assert_editor_state(
 9771        &"
 9772            🏀{{{)ˇ}}}
 9773            ε{{{)ˇ}}}
 9774            ❤️{{{)ˇ}}}
 9775        "
 9776        .unindent(),
 9777    );
 9778
 9779    // skip over the auto-closed brackets when typing a closing bracket
 9780    cx.update_editor(|editor, window, cx| {
 9781        editor.move_right(&MoveRight, window, cx);
 9782        editor.handle_input("}", window, cx);
 9783        editor.handle_input("}", window, cx);
 9784        editor.handle_input("}", window, cx);
 9785    });
 9786    cx.assert_editor_state(
 9787        &"
 9788            🏀{{{)}}}}ˇ
 9789            ε{{{)}}}}ˇ
 9790            ❤️{{{)}}}}ˇ
 9791        "
 9792        .unindent(),
 9793    );
 9794
 9795    // autoclose multi-character pairs
 9796    cx.set_state(
 9797        &"
 9798            ˇ
 9799            ˇ
 9800        "
 9801        .unindent(),
 9802    );
 9803    cx.update_editor(|editor, window, cx| {
 9804        editor.handle_input("/", window, cx);
 9805        editor.handle_input("*", window, cx);
 9806    });
 9807    cx.assert_editor_state(
 9808        &"
 9809            /*ˇ */
 9810            /*ˇ */
 9811        "
 9812        .unindent(),
 9813    );
 9814
 9815    // one cursor autocloses a multi-character pair, one cursor
 9816    // does not autoclose.
 9817    cx.set_state(
 9818        &"
 9819 9820            ˇ
 9821        "
 9822        .unindent(),
 9823    );
 9824    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 9825    cx.assert_editor_state(
 9826        &"
 9827            /*ˇ */
 9828 9829        "
 9830        .unindent(),
 9831    );
 9832
 9833    // Don't autoclose if the next character isn't whitespace and isn't
 9834    // listed in the language's "autoclose_before" section.
 9835    cx.set_state("ˇa b");
 9836    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9837    cx.assert_editor_state("{ˇa b");
 9838
 9839    // Don't autoclose if `close` is false for the bracket pair
 9840    cx.set_state("ˇ");
 9841    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 9842    cx.assert_editor_state("");
 9843
 9844    // Surround with brackets if text is selected
 9845    cx.set_state("«aˇ» b");
 9846    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9847    cx.assert_editor_state("{«aˇ»} b");
 9848
 9849    // Autoclose when not immediately after a word character
 9850    cx.set_state("a ˇ");
 9851    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9852    cx.assert_editor_state("a \"ˇ\"");
 9853
 9854    // Autoclose pair where the start and end characters are the same
 9855    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9856    cx.assert_editor_state("a \"\"ˇ");
 9857
 9858    // Don't autoclose when immediately after a word character
 9859    cx.set_state("");
 9860    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9861    cx.assert_editor_state("a\"ˇ");
 9862
 9863    // Do autoclose when after a non-word character
 9864    cx.set_state("");
 9865    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9866    cx.assert_editor_state("{\"ˇ\"");
 9867
 9868    // Non identical pairs autoclose regardless of preceding character
 9869    cx.set_state("");
 9870    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9871    cx.assert_editor_state("a{ˇ}");
 9872
 9873    // Don't autoclose pair if autoclose is disabled
 9874    cx.set_state("ˇ");
 9875    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9876    cx.assert_editor_state("");
 9877
 9878    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 9879    cx.set_state("«aˇ» b");
 9880    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9881    cx.assert_editor_state("<«aˇ»> b");
 9882}
 9883
 9884#[gpui::test]
 9885async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 9886    init_test(cx, |settings| {
 9887        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9888    });
 9889
 9890    let mut cx = EditorTestContext::new(cx).await;
 9891
 9892    let language = Arc::new(Language::new(
 9893        LanguageConfig {
 9894            brackets: BracketPairConfig {
 9895                pairs: vec![
 9896                    BracketPair {
 9897                        start: "{".to_string(),
 9898                        end: "}".to_string(),
 9899                        close: true,
 9900                        surround: true,
 9901                        newline: true,
 9902                    },
 9903                    BracketPair {
 9904                        start: "(".to_string(),
 9905                        end: ")".to_string(),
 9906                        close: true,
 9907                        surround: true,
 9908                        newline: true,
 9909                    },
 9910                    BracketPair {
 9911                        start: "[".to_string(),
 9912                        end: "]".to_string(),
 9913                        close: false,
 9914                        surround: false,
 9915                        newline: true,
 9916                    },
 9917                ],
 9918                ..Default::default()
 9919            },
 9920            autoclose_before: "})]".to_string(),
 9921            ..Default::default()
 9922        },
 9923        Some(tree_sitter_rust::LANGUAGE.into()),
 9924    ));
 9925
 9926    cx.language_registry().add(language.clone());
 9927    cx.update_buffer(|buffer, cx| {
 9928        buffer.set_language(Some(language), cx);
 9929    });
 9930
 9931    cx.set_state(
 9932        &"
 9933            ˇ
 9934            ˇ
 9935            ˇ
 9936        "
 9937        .unindent(),
 9938    );
 9939
 9940    // ensure only matching closing brackets are skipped over
 9941    cx.update_editor(|editor, window, cx| {
 9942        editor.handle_input("}", window, cx);
 9943        editor.move_left(&MoveLeft, window, cx);
 9944        editor.handle_input(")", window, cx);
 9945        editor.move_left(&MoveLeft, window, cx);
 9946    });
 9947    cx.assert_editor_state(
 9948        &"
 9949            ˇ)}
 9950            ˇ)}
 9951            ˇ)}
 9952        "
 9953        .unindent(),
 9954    );
 9955
 9956    // skip-over closing brackets at multiple cursors
 9957    cx.update_editor(|editor, window, cx| {
 9958        editor.handle_input(")", window, cx);
 9959        editor.handle_input("}", window, cx);
 9960    });
 9961    cx.assert_editor_state(
 9962        &"
 9963            )}ˇ
 9964            )}ˇ
 9965            )}ˇ
 9966        "
 9967        .unindent(),
 9968    );
 9969
 9970    // ignore non-close brackets
 9971    cx.update_editor(|editor, window, cx| {
 9972        editor.handle_input("]", window, cx);
 9973        editor.move_left(&MoveLeft, window, cx);
 9974        editor.handle_input("]", window, cx);
 9975    });
 9976    cx.assert_editor_state(
 9977        &"
 9978            )}]ˇ]
 9979            )}]ˇ]
 9980            )}]ˇ]
 9981        "
 9982        .unindent(),
 9983    );
 9984}
 9985
 9986#[gpui::test]
 9987async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 9988    init_test(cx, |_| {});
 9989
 9990    let mut cx = EditorTestContext::new(cx).await;
 9991
 9992    let html_language = Arc::new(
 9993        Language::new(
 9994            LanguageConfig {
 9995                name: "HTML".into(),
 9996                brackets: BracketPairConfig {
 9997                    pairs: vec![
 9998                        BracketPair {
 9999                            start: "<".into(),
10000                            end: ">".into(),
10001                            close: true,
10002                            ..Default::default()
10003                        },
10004                        BracketPair {
10005                            start: "{".into(),
10006                            end: "}".into(),
10007                            close: true,
10008                            ..Default::default()
10009                        },
10010                        BracketPair {
10011                            start: "(".into(),
10012                            end: ")".into(),
10013                            close: true,
10014                            ..Default::default()
10015                        },
10016                    ],
10017                    ..Default::default()
10018                },
10019                autoclose_before: "})]>".into(),
10020                ..Default::default()
10021            },
10022            Some(tree_sitter_html::LANGUAGE.into()),
10023        )
10024        .with_injection_query(
10025            r#"
10026            (script_element
10027                (raw_text) @injection.content
10028                (#set! injection.language "javascript"))
10029            "#,
10030        )
10031        .unwrap(),
10032    );
10033
10034    let javascript_language = Arc::new(Language::new(
10035        LanguageConfig {
10036            name: "JavaScript".into(),
10037            brackets: BracketPairConfig {
10038                pairs: vec![
10039                    BracketPair {
10040                        start: "/*".into(),
10041                        end: " */".into(),
10042                        close: true,
10043                        ..Default::default()
10044                    },
10045                    BracketPair {
10046                        start: "{".into(),
10047                        end: "}".into(),
10048                        close: true,
10049                        ..Default::default()
10050                    },
10051                    BracketPair {
10052                        start: "(".into(),
10053                        end: ")".into(),
10054                        close: true,
10055                        ..Default::default()
10056                    },
10057                ],
10058                ..Default::default()
10059            },
10060            autoclose_before: "})]>".into(),
10061            ..Default::default()
10062        },
10063        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10064    ));
10065
10066    cx.language_registry().add(html_language.clone());
10067    cx.language_registry().add(javascript_language);
10068    cx.executor().run_until_parked();
10069
10070    cx.update_buffer(|buffer, cx| {
10071        buffer.set_language(Some(html_language), cx);
10072    });
10073
10074    cx.set_state(
10075        &r#"
10076            <body>ˇ
10077                <script>
10078                    var x = 1;ˇ
10079                </script>
10080            </body>ˇ
10081        "#
10082        .unindent(),
10083    );
10084
10085    // Precondition: different languages are active at different locations.
10086    cx.update_editor(|editor, window, cx| {
10087        let snapshot = editor.snapshot(window, cx);
10088        let cursors = editor.selections.ranges::<usize>(cx);
10089        let languages = cursors
10090            .iter()
10091            .map(|c| snapshot.language_at(c.start).unwrap().name())
10092            .collect::<Vec<_>>();
10093        assert_eq!(
10094            languages,
10095            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10096        );
10097    });
10098
10099    // Angle brackets autoclose in HTML, but not JavaScript.
10100    cx.update_editor(|editor, window, cx| {
10101        editor.handle_input("<", window, cx);
10102        editor.handle_input("a", window, cx);
10103    });
10104    cx.assert_editor_state(
10105        &r#"
10106            <body><aˇ>
10107                <script>
10108                    var x = 1;<aˇ
10109                </script>
10110            </body><aˇ>
10111        "#
10112        .unindent(),
10113    );
10114
10115    // Curly braces and parens autoclose in both HTML and JavaScript.
10116    cx.update_editor(|editor, window, cx| {
10117        editor.handle_input(" b=", window, cx);
10118        editor.handle_input("{", window, cx);
10119        editor.handle_input("c", window, cx);
10120        editor.handle_input("(", window, cx);
10121    });
10122    cx.assert_editor_state(
10123        &r#"
10124            <body><a b={c(ˇ)}>
10125                <script>
10126                    var x = 1;<a b={c(ˇ)}
10127                </script>
10128            </body><a b={c(ˇ)}>
10129        "#
10130        .unindent(),
10131    );
10132
10133    // Brackets that were already autoclosed are skipped.
10134    cx.update_editor(|editor, window, cx| {
10135        editor.handle_input(")", window, cx);
10136        editor.handle_input("d", window, cx);
10137        editor.handle_input("}", window, cx);
10138    });
10139    cx.assert_editor_state(
10140        &r#"
10141            <body><a b={c()d}ˇ>
10142                <script>
10143                    var x = 1;<a b={c()d}ˇ
10144                </script>
10145            </body><a b={c()d}ˇ>
10146        "#
10147        .unindent(),
10148    );
10149    cx.update_editor(|editor, window, cx| {
10150        editor.handle_input(">", window, cx);
10151    });
10152    cx.assert_editor_state(
10153        &r#"
10154            <body><a b={c()d}>ˇ
10155                <script>
10156                    var x = 1;<a b={c()d}>ˇ
10157                </script>
10158            </body><a b={c()d}>ˇ
10159        "#
10160        .unindent(),
10161    );
10162
10163    // Reset
10164    cx.set_state(
10165        &r#"
10166            <body>ˇ
10167                <script>
10168                    var x = 1;ˇ
10169                </script>
10170            </body>ˇ
10171        "#
10172        .unindent(),
10173    );
10174
10175    cx.update_editor(|editor, window, cx| {
10176        editor.handle_input("<", window, cx);
10177    });
10178    cx.assert_editor_state(
10179        &r#"
10180            <body><ˇ>
10181                <script>
10182                    var x = 1;<ˇ
10183                </script>
10184            </body><ˇ>
10185        "#
10186        .unindent(),
10187    );
10188
10189    // When backspacing, the closing angle brackets are removed.
10190    cx.update_editor(|editor, window, cx| {
10191        editor.backspace(&Backspace, window, cx);
10192    });
10193    cx.assert_editor_state(
10194        &r#"
10195            <body>ˇ
10196                <script>
10197                    var x = 1;ˇ
10198                </script>
10199            </body>ˇ
10200        "#
10201        .unindent(),
10202    );
10203
10204    // Block comments autoclose in JavaScript, but not HTML.
10205    cx.update_editor(|editor, window, cx| {
10206        editor.handle_input("/", window, cx);
10207        editor.handle_input("*", window, cx);
10208    });
10209    cx.assert_editor_state(
10210        &r#"
10211            <body>/*ˇ
10212                <script>
10213                    var x = 1;/*ˇ */
10214                </script>
10215            </body>/*ˇ
10216        "#
10217        .unindent(),
10218    );
10219}
10220
10221#[gpui::test]
10222async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10223    init_test(cx, |_| {});
10224
10225    let mut cx = EditorTestContext::new(cx).await;
10226
10227    let rust_language = Arc::new(
10228        Language::new(
10229            LanguageConfig {
10230                name: "Rust".into(),
10231                brackets: serde_json::from_value(json!([
10232                    { "start": "{", "end": "}", "close": true, "newline": true },
10233                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10234                ]))
10235                .unwrap(),
10236                autoclose_before: "})]>".into(),
10237                ..Default::default()
10238            },
10239            Some(tree_sitter_rust::LANGUAGE.into()),
10240        )
10241        .with_override_query("(string_literal) @string")
10242        .unwrap(),
10243    );
10244
10245    cx.language_registry().add(rust_language.clone());
10246    cx.update_buffer(|buffer, cx| {
10247        buffer.set_language(Some(rust_language), cx);
10248    });
10249
10250    cx.set_state(
10251        &r#"
10252            let x = ˇ
10253        "#
10254        .unindent(),
10255    );
10256
10257    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10258    cx.update_editor(|editor, window, cx| {
10259        editor.handle_input("\"", window, cx);
10260    });
10261    cx.assert_editor_state(
10262        &r#"
10263            let x = "ˇ"
10264        "#
10265        .unindent(),
10266    );
10267
10268    // Inserting another quotation mark. The cursor moves across the existing
10269    // automatically-inserted quotation mark.
10270    cx.update_editor(|editor, window, cx| {
10271        editor.handle_input("\"", window, cx);
10272    });
10273    cx.assert_editor_state(
10274        &r#"
10275            let x = ""ˇ
10276        "#
10277        .unindent(),
10278    );
10279
10280    // Reset
10281    cx.set_state(
10282        &r#"
10283            let x = ˇ
10284        "#
10285        .unindent(),
10286    );
10287
10288    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10289    cx.update_editor(|editor, window, cx| {
10290        editor.handle_input("\"", window, cx);
10291        editor.handle_input(" ", window, cx);
10292        editor.move_left(&Default::default(), window, cx);
10293        editor.handle_input("\\", window, cx);
10294        editor.handle_input("\"", window, cx);
10295    });
10296    cx.assert_editor_state(
10297        &r#"
10298            let x = "\"ˇ "
10299        "#
10300        .unindent(),
10301    );
10302
10303    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10304    // mark. Nothing is inserted.
10305    cx.update_editor(|editor, window, cx| {
10306        editor.move_right(&Default::default(), window, cx);
10307        editor.handle_input("\"", window, cx);
10308    });
10309    cx.assert_editor_state(
10310        &r#"
10311            let x = "\" "ˇ
10312        "#
10313        .unindent(),
10314    );
10315}
10316
10317#[gpui::test]
10318async fn test_surround_with_pair(cx: &mut TestAppContext) {
10319    init_test(cx, |_| {});
10320
10321    let language = Arc::new(Language::new(
10322        LanguageConfig {
10323            brackets: BracketPairConfig {
10324                pairs: vec![
10325                    BracketPair {
10326                        start: "{".to_string(),
10327                        end: "}".to_string(),
10328                        close: true,
10329                        surround: true,
10330                        newline: true,
10331                    },
10332                    BracketPair {
10333                        start: "/* ".to_string(),
10334                        end: "*/".to_string(),
10335                        close: true,
10336                        surround: true,
10337                        ..Default::default()
10338                    },
10339                ],
10340                ..Default::default()
10341            },
10342            ..Default::default()
10343        },
10344        Some(tree_sitter_rust::LANGUAGE.into()),
10345    ));
10346
10347    let text = r#"
10348        a
10349        b
10350        c
10351    "#
10352    .unindent();
10353
10354    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10355    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10356    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10357    editor
10358        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10359        .await;
10360
10361    editor.update_in(cx, |editor, window, cx| {
10362        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10363            s.select_display_ranges([
10364                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10365                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10366                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10367            ])
10368        });
10369
10370        editor.handle_input("{", window, cx);
10371        editor.handle_input("{", window, cx);
10372        editor.handle_input("{", window, cx);
10373        assert_eq!(
10374            editor.text(cx),
10375            "
10376                {{{a}}}
10377                {{{b}}}
10378                {{{c}}}
10379            "
10380            .unindent()
10381        );
10382        assert_eq!(
10383            editor.selections.display_ranges(cx),
10384            [
10385                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10386                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10387                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10388            ]
10389        );
10390
10391        editor.undo(&Undo, window, cx);
10392        editor.undo(&Undo, window, cx);
10393        editor.undo(&Undo, window, cx);
10394        assert_eq!(
10395            editor.text(cx),
10396            "
10397                a
10398                b
10399                c
10400            "
10401            .unindent()
10402        );
10403        assert_eq!(
10404            editor.selections.display_ranges(cx),
10405            [
10406                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10407                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10408                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10409            ]
10410        );
10411
10412        // Ensure inserting the first character of a multi-byte bracket pair
10413        // doesn't surround the selections with the bracket.
10414        editor.handle_input("/", window, cx);
10415        assert_eq!(
10416            editor.text(cx),
10417            "
10418                /
10419                /
10420                /
10421            "
10422            .unindent()
10423        );
10424        assert_eq!(
10425            editor.selections.display_ranges(cx),
10426            [
10427                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10428                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10429                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10430            ]
10431        );
10432
10433        editor.undo(&Undo, window, cx);
10434        assert_eq!(
10435            editor.text(cx),
10436            "
10437                a
10438                b
10439                c
10440            "
10441            .unindent()
10442        );
10443        assert_eq!(
10444            editor.selections.display_ranges(cx),
10445            [
10446                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10447                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10448                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10449            ]
10450        );
10451
10452        // Ensure inserting the last character of a multi-byte bracket pair
10453        // doesn't surround the selections with the bracket.
10454        editor.handle_input("*", window, cx);
10455        assert_eq!(
10456            editor.text(cx),
10457            "
10458                *
10459                *
10460                *
10461            "
10462            .unindent()
10463        );
10464        assert_eq!(
10465            editor.selections.display_ranges(cx),
10466            [
10467                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10468                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10469                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10470            ]
10471        );
10472    });
10473}
10474
10475#[gpui::test]
10476async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10477    init_test(cx, |_| {});
10478
10479    let language = Arc::new(Language::new(
10480        LanguageConfig {
10481            brackets: BracketPairConfig {
10482                pairs: vec![BracketPair {
10483                    start: "{".to_string(),
10484                    end: "}".to_string(),
10485                    close: true,
10486                    surround: true,
10487                    newline: true,
10488                }],
10489                ..Default::default()
10490            },
10491            autoclose_before: "}".to_string(),
10492            ..Default::default()
10493        },
10494        Some(tree_sitter_rust::LANGUAGE.into()),
10495    ));
10496
10497    let text = r#"
10498        a
10499        b
10500        c
10501    "#
10502    .unindent();
10503
10504    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10505    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10506    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10507    editor
10508        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10509        .await;
10510
10511    editor.update_in(cx, |editor, window, cx| {
10512        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10513            s.select_ranges([
10514                Point::new(0, 1)..Point::new(0, 1),
10515                Point::new(1, 1)..Point::new(1, 1),
10516                Point::new(2, 1)..Point::new(2, 1),
10517            ])
10518        });
10519
10520        editor.handle_input("{", window, cx);
10521        editor.handle_input("{", window, cx);
10522        editor.handle_input("_", window, cx);
10523        assert_eq!(
10524            editor.text(cx),
10525            "
10526                a{{_}}
10527                b{{_}}
10528                c{{_}}
10529            "
10530            .unindent()
10531        );
10532        assert_eq!(
10533            editor.selections.ranges::<Point>(cx),
10534            [
10535                Point::new(0, 4)..Point::new(0, 4),
10536                Point::new(1, 4)..Point::new(1, 4),
10537                Point::new(2, 4)..Point::new(2, 4)
10538            ]
10539        );
10540
10541        editor.backspace(&Default::default(), window, cx);
10542        editor.backspace(&Default::default(), window, cx);
10543        assert_eq!(
10544            editor.text(cx),
10545            "
10546                a{}
10547                b{}
10548                c{}
10549            "
10550            .unindent()
10551        );
10552        assert_eq!(
10553            editor.selections.ranges::<Point>(cx),
10554            [
10555                Point::new(0, 2)..Point::new(0, 2),
10556                Point::new(1, 2)..Point::new(1, 2),
10557                Point::new(2, 2)..Point::new(2, 2)
10558            ]
10559        );
10560
10561        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10562        assert_eq!(
10563            editor.text(cx),
10564            "
10565                a
10566                b
10567                c
10568            "
10569            .unindent()
10570        );
10571        assert_eq!(
10572            editor.selections.ranges::<Point>(cx),
10573            [
10574                Point::new(0, 1)..Point::new(0, 1),
10575                Point::new(1, 1)..Point::new(1, 1),
10576                Point::new(2, 1)..Point::new(2, 1)
10577            ]
10578        );
10579    });
10580}
10581
10582#[gpui::test]
10583async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10584    init_test(cx, |settings| {
10585        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10586    });
10587
10588    let mut cx = EditorTestContext::new(cx).await;
10589
10590    let language = Arc::new(Language::new(
10591        LanguageConfig {
10592            brackets: BracketPairConfig {
10593                pairs: vec![
10594                    BracketPair {
10595                        start: "{".to_string(),
10596                        end: "}".to_string(),
10597                        close: true,
10598                        surround: true,
10599                        newline: true,
10600                    },
10601                    BracketPair {
10602                        start: "(".to_string(),
10603                        end: ")".to_string(),
10604                        close: true,
10605                        surround: true,
10606                        newline: true,
10607                    },
10608                    BracketPair {
10609                        start: "[".to_string(),
10610                        end: "]".to_string(),
10611                        close: false,
10612                        surround: true,
10613                        newline: true,
10614                    },
10615                ],
10616                ..Default::default()
10617            },
10618            autoclose_before: "})]".to_string(),
10619            ..Default::default()
10620        },
10621        Some(tree_sitter_rust::LANGUAGE.into()),
10622    ));
10623
10624    cx.language_registry().add(language.clone());
10625    cx.update_buffer(|buffer, cx| {
10626        buffer.set_language(Some(language), cx);
10627    });
10628
10629    cx.set_state(
10630        &"
10631            {(ˇ)}
10632            [[ˇ]]
10633            {(ˇ)}
10634        "
10635        .unindent(),
10636    );
10637
10638    cx.update_editor(|editor, window, cx| {
10639        editor.backspace(&Default::default(), window, cx);
10640        editor.backspace(&Default::default(), window, cx);
10641    });
10642
10643    cx.assert_editor_state(
10644        &"
10645            ˇ
10646            ˇ]]
10647            ˇ
10648        "
10649        .unindent(),
10650    );
10651
10652    cx.update_editor(|editor, window, cx| {
10653        editor.handle_input("{", window, cx);
10654        editor.handle_input("{", window, cx);
10655        editor.move_right(&MoveRight, window, cx);
10656        editor.move_right(&MoveRight, window, cx);
10657        editor.move_left(&MoveLeft, window, cx);
10658        editor.move_left(&MoveLeft, window, cx);
10659        editor.backspace(&Default::default(), window, cx);
10660    });
10661
10662    cx.assert_editor_state(
10663        &"
10664            {ˇ}
10665            {ˇ}]]
10666            {ˇ}
10667        "
10668        .unindent(),
10669    );
10670
10671    cx.update_editor(|editor, window, cx| {
10672        editor.backspace(&Default::default(), window, cx);
10673    });
10674
10675    cx.assert_editor_state(
10676        &"
10677            ˇ
10678            ˇ]]
10679            ˇ
10680        "
10681        .unindent(),
10682    );
10683}
10684
10685#[gpui::test]
10686async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10687    init_test(cx, |_| {});
10688
10689    let language = Arc::new(Language::new(
10690        LanguageConfig::default(),
10691        Some(tree_sitter_rust::LANGUAGE.into()),
10692    ));
10693
10694    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10695    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10696    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10697    editor
10698        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10699        .await;
10700
10701    editor.update_in(cx, |editor, window, cx| {
10702        editor.set_auto_replace_emoji_shortcode(true);
10703
10704        editor.handle_input("Hello ", window, cx);
10705        editor.handle_input(":wave", window, cx);
10706        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10707
10708        editor.handle_input(":", window, cx);
10709        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10710
10711        editor.handle_input(" :smile", window, cx);
10712        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10713
10714        editor.handle_input(":", window, cx);
10715        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10716
10717        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10718        editor.handle_input(":wave", window, cx);
10719        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10720
10721        editor.handle_input(":", window, cx);
10722        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10723
10724        editor.handle_input(":1", window, cx);
10725        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10726
10727        editor.handle_input(":", window, cx);
10728        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
10729
10730        // Ensure shortcode does not get replaced when it is part of a word
10731        editor.handle_input(" Test:wave", window, cx);
10732        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
10733
10734        editor.handle_input(":", window, cx);
10735        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
10736
10737        editor.set_auto_replace_emoji_shortcode(false);
10738
10739        // Ensure shortcode does not get replaced when auto replace is off
10740        editor.handle_input(" :wave", window, cx);
10741        assert_eq!(
10742            editor.text(cx),
10743            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
10744        );
10745
10746        editor.handle_input(":", window, cx);
10747        assert_eq!(
10748            editor.text(cx),
10749            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
10750        );
10751    });
10752}
10753
10754#[gpui::test]
10755async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
10756    init_test(cx, |_| {});
10757
10758    let (text, insertion_ranges) = marked_text_ranges(
10759        indoc! {"
10760            ˇ
10761        "},
10762        false,
10763    );
10764
10765    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
10766    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10767
10768    _ = editor.update_in(cx, |editor, window, cx| {
10769        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
10770
10771        editor
10772            .insert_snippet(&insertion_ranges, snippet, window, cx)
10773            .unwrap();
10774
10775        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
10776            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
10777            assert_eq!(editor.text(cx), expected_text);
10778            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
10779        }
10780
10781        assert(
10782            editor,
10783            cx,
10784            indoc! {"
10785            type «» =•
10786            "},
10787        );
10788
10789        assert!(editor.context_menu_visible(), "There should be a matches");
10790    });
10791}
10792
10793#[gpui::test]
10794async fn test_snippets(cx: &mut TestAppContext) {
10795    init_test(cx, |_| {});
10796
10797    let mut cx = EditorTestContext::new(cx).await;
10798
10799    cx.set_state(indoc! {"
10800        a.ˇ b
10801        a.ˇ b
10802        a.ˇ b
10803    "});
10804
10805    cx.update_editor(|editor, window, cx| {
10806        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
10807        let insertion_ranges = editor
10808            .selections
10809            .all(cx)
10810            .iter()
10811            .map(|s| s.range())
10812            .collect::<Vec<_>>();
10813        editor
10814            .insert_snippet(&insertion_ranges, snippet, window, cx)
10815            .unwrap();
10816    });
10817
10818    cx.assert_editor_state(indoc! {"
10819        a.f(«oneˇ», two, «threeˇ») b
10820        a.f(«oneˇ», two, «threeˇ») b
10821        a.f(«oneˇ», two, «threeˇ») b
10822    "});
10823
10824    // Can't move earlier than the first tab stop
10825    cx.update_editor(|editor, window, cx| {
10826        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10827    });
10828    cx.assert_editor_state(indoc! {"
10829        a.f(«oneˇ», two, «threeˇ») b
10830        a.f(«oneˇ», two, «threeˇ») b
10831        a.f(«oneˇ», two, «threeˇ») b
10832    "});
10833
10834    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10835    cx.assert_editor_state(indoc! {"
10836        a.f(one, «twoˇ», three) b
10837        a.f(one, «twoˇ», three) b
10838        a.f(one, «twoˇ», three) b
10839    "});
10840
10841    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
10842    cx.assert_editor_state(indoc! {"
10843        a.f(«oneˇ», two, «threeˇ») b
10844        a.f(«oneˇ», two, «threeˇ») b
10845        a.f(«oneˇ», two, «threeˇ») b
10846    "});
10847
10848    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10849    cx.assert_editor_state(indoc! {"
10850        a.f(one, «twoˇ», three) b
10851        a.f(one, «twoˇ», three) b
10852        a.f(one, «twoˇ», three) b
10853    "});
10854    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10855    cx.assert_editor_state(indoc! {"
10856        a.f(one, two, three)ˇ b
10857        a.f(one, two, three)ˇ b
10858        a.f(one, two, three)ˇ b
10859    "});
10860
10861    // As soon as the last tab stop is reached, snippet state is gone
10862    cx.update_editor(|editor, window, cx| {
10863        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10864    });
10865    cx.assert_editor_state(indoc! {"
10866        a.f(one, two, three)ˇ b
10867        a.f(one, two, three)ˇ b
10868        a.f(one, two, three)ˇ b
10869    "});
10870}
10871
10872#[gpui::test]
10873async fn test_snippet_indentation(cx: &mut TestAppContext) {
10874    init_test(cx, |_| {});
10875
10876    let mut cx = EditorTestContext::new(cx).await;
10877
10878    cx.update_editor(|editor, window, cx| {
10879        let snippet = Snippet::parse(indoc! {"
10880            /*
10881             * Multiline comment with leading indentation
10882             *
10883             * $1
10884             */
10885            $0"})
10886        .unwrap();
10887        let insertion_ranges = editor
10888            .selections
10889            .all(cx)
10890            .iter()
10891            .map(|s| s.range())
10892            .collect::<Vec<_>>();
10893        editor
10894            .insert_snippet(&insertion_ranges, snippet, window, cx)
10895            .unwrap();
10896    });
10897
10898    cx.assert_editor_state(indoc! {"
10899        /*
10900         * Multiline comment with leading indentation
10901         *
10902         * ˇ
10903         */
10904    "});
10905
10906    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10907    cx.assert_editor_state(indoc! {"
10908        /*
10909         * Multiline comment with leading indentation
10910         *
10911         *•
10912         */
10913        ˇ"});
10914}
10915
10916#[gpui::test]
10917async fn test_document_format_during_save(cx: &mut TestAppContext) {
10918    init_test(cx, |_| {});
10919
10920    let fs = FakeFs::new(cx.executor());
10921    fs.insert_file(path!("/file.rs"), Default::default()).await;
10922
10923    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
10924
10925    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10926    language_registry.add(rust_lang());
10927    let mut fake_servers = language_registry.register_fake_lsp(
10928        "Rust",
10929        FakeLspAdapter {
10930            capabilities: lsp::ServerCapabilities {
10931                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10932                ..Default::default()
10933            },
10934            ..Default::default()
10935        },
10936    );
10937
10938    let buffer = project
10939        .update(cx, |project, cx| {
10940            project.open_local_buffer(path!("/file.rs"), cx)
10941        })
10942        .await
10943        .unwrap();
10944
10945    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10946    let (editor, cx) = cx.add_window_view(|window, cx| {
10947        build_editor_with_project(project.clone(), buffer, window, cx)
10948    });
10949    editor.update_in(cx, |editor, window, cx| {
10950        editor.set_text("one\ntwo\nthree\n", window, cx)
10951    });
10952    assert!(cx.read(|cx| editor.is_dirty(cx)));
10953
10954    cx.executor().start_waiting();
10955    let fake_server = fake_servers.next().await.unwrap();
10956
10957    {
10958        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10959            move |params, _| async move {
10960                assert_eq!(
10961                    params.text_document.uri,
10962                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10963                );
10964                assert_eq!(params.options.tab_size, 4);
10965                Ok(Some(vec![lsp::TextEdit::new(
10966                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10967                    ", ".to_string(),
10968                )]))
10969            },
10970        );
10971        let save = editor
10972            .update_in(cx, |editor, window, cx| {
10973                editor.save(
10974                    SaveOptions {
10975                        format: true,
10976                        autosave: false,
10977                    },
10978                    project.clone(),
10979                    window,
10980                    cx,
10981                )
10982            })
10983            .unwrap();
10984        cx.executor().start_waiting();
10985        save.await;
10986
10987        assert_eq!(
10988            editor.update(cx, |editor, cx| editor.text(cx)),
10989            "one, two\nthree\n"
10990        );
10991        assert!(!cx.read(|cx| editor.is_dirty(cx)));
10992    }
10993
10994    {
10995        editor.update_in(cx, |editor, window, cx| {
10996            editor.set_text("one\ntwo\nthree\n", window, cx)
10997        });
10998        assert!(cx.read(|cx| editor.is_dirty(cx)));
10999
11000        // Ensure we can still save even if formatting hangs.
11001        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11002            move |params, _| async move {
11003                assert_eq!(
11004                    params.text_document.uri,
11005                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11006                );
11007                futures::future::pending::<()>().await;
11008                unreachable!()
11009            },
11010        );
11011        let save = editor
11012            .update_in(cx, |editor, window, cx| {
11013                editor.save(
11014                    SaveOptions {
11015                        format: true,
11016                        autosave: false,
11017                    },
11018                    project.clone(),
11019                    window,
11020                    cx,
11021                )
11022            })
11023            .unwrap();
11024        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11025        cx.executor().start_waiting();
11026        save.await;
11027        assert_eq!(
11028            editor.update(cx, |editor, cx| editor.text(cx)),
11029            "one\ntwo\nthree\n"
11030        );
11031    }
11032
11033    // Set rust language override and assert overridden tabsize is sent to language server
11034    update_test_language_settings(cx, |settings| {
11035        settings.languages.0.insert(
11036            "Rust".into(),
11037            LanguageSettingsContent {
11038                tab_size: NonZeroU32::new(8),
11039                ..Default::default()
11040            },
11041        );
11042    });
11043
11044    {
11045        editor.update_in(cx, |editor, window, cx| {
11046            editor.set_text("somehting_new\n", window, cx)
11047        });
11048        assert!(cx.read(|cx| editor.is_dirty(cx)));
11049        let _formatting_request_signal = fake_server
11050            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11051                assert_eq!(
11052                    params.text_document.uri,
11053                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11054                );
11055                assert_eq!(params.options.tab_size, 8);
11056                Ok(Some(vec![]))
11057            });
11058        let save = editor
11059            .update_in(cx, |editor, window, cx| {
11060                editor.save(
11061                    SaveOptions {
11062                        format: true,
11063                        autosave: false,
11064                    },
11065                    project.clone(),
11066                    window,
11067                    cx,
11068                )
11069            })
11070            .unwrap();
11071        cx.executor().start_waiting();
11072        save.await;
11073    }
11074}
11075
11076#[gpui::test]
11077async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11078    init_test(cx, |settings| {
11079        settings.defaults.ensure_final_newline_on_save = Some(false);
11080    });
11081
11082    let fs = FakeFs::new(cx.executor());
11083    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11084
11085    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11086
11087    let buffer = project
11088        .update(cx, |project, cx| {
11089            project.open_local_buffer(path!("/file.txt"), cx)
11090        })
11091        .await
11092        .unwrap();
11093
11094    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11095    let (editor, cx) = cx.add_window_view(|window, cx| {
11096        build_editor_with_project(project.clone(), buffer, window, cx)
11097    });
11098    editor.update_in(cx, |editor, window, cx| {
11099        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11100            s.select_ranges([0..0])
11101        });
11102    });
11103    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11104
11105    editor.update_in(cx, |editor, window, cx| {
11106        editor.handle_input("\n", window, cx)
11107    });
11108    cx.run_until_parked();
11109    save(&editor, &project, cx).await;
11110    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11111
11112    editor.update_in(cx, |editor, window, cx| {
11113        editor.undo(&Default::default(), window, cx);
11114    });
11115    save(&editor, &project, cx).await;
11116    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11117
11118    editor.update_in(cx, |editor, window, cx| {
11119        editor.redo(&Default::default(), window, cx);
11120    });
11121    cx.run_until_parked();
11122    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11123
11124    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11125        let save = editor
11126            .update_in(cx, |editor, window, cx| {
11127                editor.save(
11128                    SaveOptions {
11129                        format: true,
11130                        autosave: false,
11131                    },
11132                    project.clone(),
11133                    window,
11134                    cx,
11135                )
11136            })
11137            .unwrap();
11138        cx.executor().start_waiting();
11139        save.await;
11140        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11141    }
11142}
11143
11144#[gpui::test]
11145async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11146    init_test(cx, |_| {});
11147
11148    let cols = 4;
11149    let rows = 10;
11150    let sample_text_1 = sample_text(rows, cols, 'a');
11151    assert_eq!(
11152        sample_text_1,
11153        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11154    );
11155    let sample_text_2 = sample_text(rows, cols, 'l');
11156    assert_eq!(
11157        sample_text_2,
11158        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11159    );
11160    let sample_text_3 = sample_text(rows, cols, 'v');
11161    assert_eq!(
11162        sample_text_3,
11163        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11164    );
11165
11166    let fs = FakeFs::new(cx.executor());
11167    fs.insert_tree(
11168        path!("/a"),
11169        json!({
11170            "main.rs": sample_text_1,
11171            "other.rs": sample_text_2,
11172            "lib.rs": sample_text_3,
11173        }),
11174    )
11175    .await;
11176
11177    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11178    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11179    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11180
11181    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11182    language_registry.add(rust_lang());
11183    let mut fake_servers = language_registry.register_fake_lsp(
11184        "Rust",
11185        FakeLspAdapter {
11186            capabilities: lsp::ServerCapabilities {
11187                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11188                ..Default::default()
11189            },
11190            ..Default::default()
11191        },
11192    );
11193
11194    let worktree = project.update(cx, |project, cx| {
11195        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11196        assert_eq!(worktrees.len(), 1);
11197        worktrees.pop().unwrap()
11198    });
11199    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11200
11201    let buffer_1 = project
11202        .update(cx, |project, cx| {
11203            project.open_buffer((worktree_id, "main.rs"), cx)
11204        })
11205        .await
11206        .unwrap();
11207    let buffer_2 = project
11208        .update(cx, |project, cx| {
11209            project.open_buffer((worktree_id, "other.rs"), cx)
11210        })
11211        .await
11212        .unwrap();
11213    let buffer_3 = project
11214        .update(cx, |project, cx| {
11215            project.open_buffer((worktree_id, "lib.rs"), cx)
11216        })
11217        .await
11218        .unwrap();
11219
11220    let multi_buffer = cx.new(|cx| {
11221        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11222        multi_buffer.push_excerpts(
11223            buffer_1.clone(),
11224            [
11225                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11226                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11227                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11228            ],
11229            cx,
11230        );
11231        multi_buffer.push_excerpts(
11232            buffer_2.clone(),
11233            [
11234                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11235                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11236                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11237            ],
11238            cx,
11239        );
11240        multi_buffer.push_excerpts(
11241            buffer_3.clone(),
11242            [
11243                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11244                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11245                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11246            ],
11247            cx,
11248        );
11249        multi_buffer
11250    });
11251    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11252        Editor::new(
11253            EditorMode::full(),
11254            multi_buffer,
11255            Some(project.clone()),
11256            window,
11257            cx,
11258        )
11259    });
11260
11261    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11262        editor.change_selections(
11263            SelectionEffects::scroll(Autoscroll::Next),
11264            window,
11265            cx,
11266            |s| s.select_ranges(Some(1..2)),
11267        );
11268        editor.insert("|one|two|three|", window, cx);
11269    });
11270    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11271    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11272        editor.change_selections(
11273            SelectionEffects::scroll(Autoscroll::Next),
11274            window,
11275            cx,
11276            |s| s.select_ranges(Some(60..70)),
11277        );
11278        editor.insert("|four|five|six|", window, cx);
11279    });
11280    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11281
11282    // First two buffers should be edited, but not the third one.
11283    assert_eq!(
11284        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11285        "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}",
11286    );
11287    buffer_1.update(cx, |buffer, _| {
11288        assert!(buffer.is_dirty());
11289        assert_eq!(
11290            buffer.text(),
11291            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11292        )
11293    });
11294    buffer_2.update(cx, |buffer, _| {
11295        assert!(buffer.is_dirty());
11296        assert_eq!(
11297            buffer.text(),
11298            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11299        )
11300    });
11301    buffer_3.update(cx, |buffer, _| {
11302        assert!(!buffer.is_dirty());
11303        assert_eq!(buffer.text(), sample_text_3,)
11304    });
11305    cx.executor().run_until_parked();
11306
11307    cx.executor().start_waiting();
11308    let save = multi_buffer_editor
11309        .update_in(cx, |editor, window, cx| {
11310            editor.save(
11311                SaveOptions {
11312                    format: true,
11313                    autosave: false,
11314                },
11315                project.clone(),
11316                window,
11317                cx,
11318            )
11319        })
11320        .unwrap();
11321
11322    let fake_server = fake_servers.next().await.unwrap();
11323    fake_server
11324        .server
11325        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11326            Ok(Some(vec![lsp::TextEdit::new(
11327                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11328                format!("[{} formatted]", params.text_document.uri),
11329            )]))
11330        })
11331        .detach();
11332    save.await;
11333
11334    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11335    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11336    assert_eq!(
11337        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11338        uri!(
11339            "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}"
11340        ),
11341    );
11342    buffer_1.update(cx, |buffer, _| {
11343        assert!(!buffer.is_dirty());
11344        assert_eq!(
11345            buffer.text(),
11346            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11347        )
11348    });
11349    buffer_2.update(cx, |buffer, _| {
11350        assert!(!buffer.is_dirty());
11351        assert_eq!(
11352            buffer.text(),
11353            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11354        )
11355    });
11356    buffer_3.update(cx, |buffer, _| {
11357        assert!(!buffer.is_dirty());
11358        assert_eq!(buffer.text(), sample_text_3,)
11359    });
11360}
11361
11362#[gpui::test]
11363async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11364    init_test(cx, |_| {});
11365
11366    let fs = FakeFs::new(cx.executor());
11367    fs.insert_tree(
11368        path!("/dir"),
11369        json!({
11370            "file1.rs": "fn main() { println!(\"hello\"); }",
11371            "file2.rs": "fn test() { println!(\"test\"); }",
11372            "file3.rs": "fn other() { println!(\"other\"); }\n",
11373        }),
11374    )
11375    .await;
11376
11377    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11378    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11379    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11380
11381    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11382    language_registry.add(rust_lang());
11383
11384    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11385    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11386
11387    // Open three buffers
11388    let buffer_1 = project
11389        .update(cx, |project, cx| {
11390            project.open_buffer((worktree_id, "file1.rs"), cx)
11391        })
11392        .await
11393        .unwrap();
11394    let buffer_2 = project
11395        .update(cx, |project, cx| {
11396            project.open_buffer((worktree_id, "file2.rs"), cx)
11397        })
11398        .await
11399        .unwrap();
11400    let buffer_3 = project
11401        .update(cx, |project, cx| {
11402            project.open_buffer((worktree_id, "file3.rs"), cx)
11403        })
11404        .await
11405        .unwrap();
11406
11407    // Create a multi-buffer with all three buffers
11408    let multi_buffer = cx.new(|cx| {
11409        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11410        multi_buffer.push_excerpts(
11411            buffer_1.clone(),
11412            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11413            cx,
11414        );
11415        multi_buffer.push_excerpts(
11416            buffer_2.clone(),
11417            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11418            cx,
11419        );
11420        multi_buffer.push_excerpts(
11421            buffer_3.clone(),
11422            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11423            cx,
11424        );
11425        multi_buffer
11426    });
11427
11428    let editor = cx.new_window_entity(|window, cx| {
11429        Editor::new(
11430            EditorMode::full(),
11431            multi_buffer,
11432            Some(project.clone()),
11433            window,
11434            cx,
11435        )
11436    });
11437
11438    // Edit only the first buffer
11439    editor.update_in(cx, |editor, window, cx| {
11440        editor.change_selections(
11441            SelectionEffects::scroll(Autoscroll::Next),
11442            window,
11443            cx,
11444            |s| s.select_ranges(Some(10..10)),
11445        );
11446        editor.insert("// edited", window, cx);
11447    });
11448
11449    // Verify that only buffer 1 is dirty
11450    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11451    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11452    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11453
11454    // Get write counts after file creation (files were created with initial content)
11455    // We expect each file to have been written once during creation
11456    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11457    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11458    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11459
11460    // Perform autosave
11461    let save_task = editor.update_in(cx, |editor, window, cx| {
11462        editor.save(
11463            SaveOptions {
11464                format: true,
11465                autosave: true,
11466            },
11467            project.clone(),
11468            window,
11469            cx,
11470        )
11471    });
11472    save_task.await.unwrap();
11473
11474    // Only the dirty buffer should have been saved
11475    assert_eq!(
11476        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11477        1,
11478        "Buffer 1 was dirty, so it should have been written once during autosave"
11479    );
11480    assert_eq!(
11481        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11482        0,
11483        "Buffer 2 was clean, so it should not have been written during autosave"
11484    );
11485    assert_eq!(
11486        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11487        0,
11488        "Buffer 3 was clean, so it should not have been written during autosave"
11489    );
11490
11491    // Verify buffer states after autosave
11492    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11493    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11494    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11495
11496    // Now perform a manual save (format = true)
11497    let save_task = editor.update_in(cx, |editor, window, cx| {
11498        editor.save(
11499            SaveOptions {
11500                format: true,
11501                autosave: false,
11502            },
11503            project.clone(),
11504            window,
11505            cx,
11506        )
11507    });
11508    save_task.await.unwrap();
11509
11510    // During manual save, clean buffers don't get written to disk
11511    // They just get did_save called for language server notifications
11512    assert_eq!(
11513        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11514        1,
11515        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11516    );
11517    assert_eq!(
11518        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11519        0,
11520        "Buffer 2 should not have been written at all"
11521    );
11522    assert_eq!(
11523        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11524        0,
11525        "Buffer 3 should not have been written at all"
11526    );
11527}
11528
11529async fn setup_range_format_test(
11530    cx: &mut TestAppContext,
11531) -> (
11532    Entity<Project>,
11533    Entity<Editor>,
11534    &mut gpui::VisualTestContext,
11535    lsp::FakeLanguageServer,
11536) {
11537    init_test(cx, |_| {});
11538
11539    let fs = FakeFs::new(cx.executor());
11540    fs.insert_file(path!("/file.rs"), Default::default()).await;
11541
11542    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11543
11544    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11545    language_registry.add(rust_lang());
11546    let mut fake_servers = language_registry.register_fake_lsp(
11547        "Rust",
11548        FakeLspAdapter {
11549            capabilities: lsp::ServerCapabilities {
11550                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11551                ..lsp::ServerCapabilities::default()
11552            },
11553            ..FakeLspAdapter::default()
11554        },
11555    );
11556
11557    let buffer = project
11558        .update(cx, |project, cx| {
11559            project.open_local_buffer(path!("/file.rs"), cx)
11560        })
11561        .await
11562        .unwrap();
11563
11564    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11565    let (editor, cx) = cx.add_window_view(|window, cx| {
11566        build_editor_with_project(project.clone(), buffer, window, cx)
11567    });
11568
11569    cx.executor().start_waiting();
11570    let fake_server = fake_servers.next().await.unwrap();
11571
11572    (project, editor, cx, fake_server)
11573}
11574
11575#[gpui::test]
11576async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11577    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11578
11579    editor.update_in(cx, |editor, window, cx| {
11580        editor.set_text("one\ntwo\nthree\n", window, cx)
11581    });
11582    assert!(cx.read(|cx| editor.is_dirty(cx)));
11583
11584    let save = editor
11585        .update_in(cx, |editor, window, cx| {
11586            editor.save(
11587                SaveOptions {
11588                    format: true,
11589                    autosave: false,
11590                },
11591                project.clone(),
11592                window,
11593                cx,
11594            )
11595        })
11596        .unwrap();
11597    fake_server
11598        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11599            assert_eq!(
11600                params.text_document.uri,
11601                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11602            );
11603            assert_eq!(params.options.tab_size, 4);
11604            Ok(Some(vec![lsp::TextEdit::new(
11605                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11606                ", ".to_string(),
11607            )]))
11608        })
11609        .next()
11610        .await;
11611    cx.executor().start_waiting();
11612    save.await;
11613    assert_eq!(
11614        editor.update(cx, |editor, cx| editor.text(cx)),
11615        "one, two\nthree\n"
11616    );
11617    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11618}
11619
11620#[gpui::test]
11621async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11622    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11623
11624    editor.update_in(cx, |editor, window, cx| {
11625        editor.set_text("one\ntwo\nthree\n", window, cx)
11626    });
11627    assert!(cx.read(|cx| editor.is_dirty(cx)));
11628
11629    // Test that save still works when formatting hangs
11630    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11631        move |params, _| async move {
11632            assert_eq!(
11633                params.text_document.uri,
11634                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11635            );
11636            futures::future::pending::<()>().await;
11637            unreachable!()
11638        },
11639    );
11640    let save = editor
11641        .update_in(cx, |editor, window, cx| {
11642            editor.save(
11643                SaveOptions {
11644                    format: true,
11645                    autosave: false,
11646                },
11647                project.clone(),
11648                window,
11649                cx,
11650            )
11651        })
11652        .unwrap();
11653    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11654    cx.executor().start_waiting();
11655    save.await;
11656    assert_eq!(
11657        editor.update(cx, |editor, cx| editor.text(cx)),
11658        "one\ntwo\nthree\n"
11659    );
11660    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11661}
11662
11663#[gpui::test]
11664async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11665    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11666
11667    // Buffer starts clean, no formatting should be requested
11668    let save = editor
11669        .update_in(cx, |editor, window, cx| {
11670            editor.save(
11671                SaveOptions {
11672                    format: false,
11673                    autosave: false,
11674                },
11675                project.clone(),
11676                window,
11677                cx,
11678            )
11679        })
11680        .unwrap();
11681    let _pending_format_request = fake_server
11682        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11683            panic!("Should not be invoked");
11684        })
11685        .next();
11686    cx.executor().start_waiting();
11687    save.await;
11688    cx.run_until_parked();
11689}
11690
11691#[gpui::test]
11692async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11693    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11694
11695    // Set Rust language override and assert overridden tabsize is sent to language server
11696    update_test_language_settings(cx, |settings| {
11697        settings.languages.0.insert(
11698            "Rust".into(),
11699            LanguageSettingsContent {
11700                tab_size: NonZeroU32::new(8),
11701                ..Default::default()
11702            },
11703        );
11704    });
11705
11706    editor.update_in(cx, |editor, window, cx| {
11707        editor.set_text("something_new\n", window, cx)
11708    });
11709    assert!(cx.read(|cx| editor.is_dirty(cx)));
11710    let save = editor
11711        .update_in(cx, |editor, window, cx| {
11712            editor.save(
11713                SaveOptions {
11714                    format: true,
11715                    autosave: false,
11716                },
11717                project.clone(),
11718                window,
11719                cx,
11720            )
11721        })
11722        .unwrap();
11723    fake_server
11724        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11725            assert_eq!(
11726                params.text_document.uri,
11727                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11728            );
11729            assert_eq!(params.options.tab_size, 8);
11730            Ok(Some(Vec::new()))
11731        })
11732        .next()
11733        .await;
11734    save.await;
11735}
11736
11737#[gpui::test]
11738async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
11739    init_test(cx, |settings| {
11740        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
11741            Formatter::LanguageServer { name: None },
11742        )))
11743    });
11744
11745    let fs = FakeFs::new(cx.executor());
11746    fs.insert_file(path!("/file.rs"), Default::default()).await;
11747
11748    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11749
11750    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11751    language_registry.add(Arc::new(Language::new(
11752        LanguageConfig {
11753            name: "Rust".into(),
11754            matcher: LanguageMatcher {
11755                path_suffixes: vec!["rs".to_string()],
11756                ..Default::default()
11757            },
11758            ..LanguageConfig::default()
11759        },
11760        Some(tree_sitter_rust::LANGUAGE.into()),
11761    )));
11762    update_test_language_settings(cx, |settings| {
11763        // Enable Prettier formatting for the same buffer, and ensure
11764        // LSP is called instead of Prettier.
11765        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
11766    });
11767    let mut fake_servers = language_registry.register_fake_lsp(
11768        "Rust",
11769        FakeLspAdapter {
11770            capabilities: lsp::ServerCapabilities {
11771                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11772                ..Default::default()
11773            },
11774            ..Default::default()
11775        },
11776    );
11777
11778    let buffer = project
11779        .update(cx, |project, cx| {
11780            project.open_local_buffer(path!("/file.rs"), cx)
11781        })
11782        .await
11783        .unwrap();
11784
11785    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11786    let (editor, cx) = cx.add_window_view(|window, cx| {
11787        build_editor_with_project(project.clone(), buffer, window, cx)
11788    });
11789    editor.update_in(cx, |editor, window, cx| {
11790        editor.set_text("one\ntwo\nthree\n", window, cx)
11791    });
11792
11793    cx.executor().start_waiting();
11794    let fake_server = fake_servers.next().await.unwrap();
11795
11796    let format = editor
11797        .update_in(cx, |editor, window, cx| {
11798            editor.perform_format(
11799                project.clone(),
11800                FormatTrigger::Manual,
11801                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11802                window,
11803                cx,
11804            )
11805        })
11806        .unwrap();
11807    fake_server
11808        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11809            assert_eq!(
11810                params.text_document.uri,
11811                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11812            );
11813            assert_eq!(params.options.tab_size, 4);
11814            Ok(Some(vec![lsp::TextEdit::new(
11815                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11816                ", ".to_string(),
11817            )]))
11818        })
11819        .next()
11820        .await;
11821    cx.executor().start_waiting();
11822    format.await;
11823    assert_eq!(
11824        editor.update(cx, |editor, cx| editor.text(cx)),
11825        "one, two\nthree\n"
11826    );
11827
11828    editor.update_in(cx, |editor, window, cx| {
11829        editor.set_text("one\ntwo\nthree\n", window, cx)
11830    });
11831    // Ensure we don't lock if formatting hangs.
11832    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11833        move |params, _| async move {
11834            assert_eq!(
11835                params.text_document.uri,
11836                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11837            );
11838            futures::future::pending::<()>().await;
11839            unreachable!()
11840        },
11841    );
11842    let format = editor
11843        .update_in(cx, |editor, window, cx| {
11844            editor.perform_format(
11845                project,
11846                FormatTrigger::Manual,
11847                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11848                window,
11849                cx,
11850            )
11851        })
11852        .unwrap();
11853    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11854    cx.executor().start_waiting();
11855    format.await;
11856    assert_eq!(
11857        editor.update(cx, |editor, cx| editor.text(cx)),
11858        "one\ntwo\nthree\n"
11859    );
11860}
11861
11862#[gpui::test]
11863async fn test_multiple_formatters(cx: &mut TestAppContext) {
11864    init_test(cx, |settings| {
11865        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
11866        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11867            Formatter::LanguageServer { name: None },
11868            Formatter::CodeActions(
11869                [
11870                    ("code-action-1".into(), true),
11871                    ("code-action-2".into(), true),
11872                ]
11873                .into_iter()
11874                .collect(),
11875            ),
11876        ])))
11877    });
11878
11879    let fs = FakeFs::new(cx.executor());
11880    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
11881        .await;
11882
11883    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11884    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11885    language_registry.add(rust_lang());
11886
11887    let mut fake_servers = language_registry.register_fake_lsp(
11888        "Rust",
11889        FakeLspAdapter {
11890            capabilities: lsp::ServerCapabilities {
11891                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11892                execute_command_provider: Some(lsp::ExecuteCommandOptions {
11893                    commands: vec!["the-command-for-code-action-1".into()],
11894                    ..Default::default()
11895                }),
11896                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11897                ..Default::default()
11898            },
11899            ..Default::default()
11900        },
11901    );
11902
11903    let buffer = project
11904        .update(cx, |project, cx| {
11905            project.open_local_buffer(path!("/file.rs"), cx)
11906        })
11907        .await
11908        .unwrap();
11909
11910    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11911    let (editor, cx) = cx.add_window_view(|window, cx| {
11912        build_editor_with_project(project.clone(), buffer, window, cx)
11913    });
11914
11915    cx.executor().start_waiting();
11916
11917    let fake_server = fake_servers.next().await.unwrap();
11918    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11919        move |_params, _| async move {
11920            Ok(Some(vec![lsp::TextEdit::new(
11921                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11922                "applied-formatting\n".to_string(),
11923            )]))
11924        },
11925    );
11926    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11927        move |params, _| async move {
11928            assert_eq!(
11929                params.context.only,
11930                Some(vec!["code-action-1".into(), "code-action-2".into()])
11931            );
11932            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
11933            Ok(Some(vec![
11934                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11935                    kind: Some("code-action-1".into()),
11936                    edit: Some(lsp::WorkspaceEdit::new(
11937                        [(
11938                            uri.clone(),
11939                            vec![lsp::TextEdit::new(
11940                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11941                                "applied-code-action-1-edit\n".to_string(),
11942                            )],
11943                        )]
11944                        .into_iter()
11945                        .collect(),
11946                    )),
11947                    command: Some(lsp::Command {
11948                        command: "the-command-for-code-action-1".into(),
11949                        ..Default::default()
11950                    }),
11951                    ..Default::default()
11952                }),
11953                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11954                    kind: Some("code-action-2".into()),
11955                    edit: Some(lsp::WorkspaceEdit::new(
11956                        [(
11957                            uri,
11958                            vec![lsp::TextEdit::new(
11959                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11960                                "applied-code-action-2-edit\n".to_string(),
11961                            )],
11962                        )]
11963                        .into_iter()
11964                        .collect(),
11965                    )),
11966                    ..Default::default()
11967                }),
11968            ]))
11969        },
11970    );
11971
11972    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
11973        move |params, _| async move { Ok(params) }
11974    });
11975
11976    let command_lock = Arc::new(futures::lock::Mutex::new(()));
11977    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
11978        let fake = fake_server.clone();
11979        let lock = command_lock.clone();
11980        move |params, _| {
11981            assert_eq!(params.command, "the-command-for-code-action-1");
11982            let fake = fake.clone();
11983            let lock = lock.clone();
11984            async move {
11985                lock.lock().await;
11986                fake.server
11987                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
11988                        label: None,
11989                        edit: lsp::WorkspaceEdit {
11990                            changes: Some(
11991                                [(
11992                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
11993                                    vec![lsp::TextEdit {
11994                                        range: lsp::Range::new(
11995                                            lsp::Position::new(0, 0),
11996                                            lsp::Position::new(0, 0),
11997                                        ),
11998                                        new_text: "applied-code-action-1-command\n".into(),
11999                                    }],
12000                                )]
12001                                .into_iter()
12002                                .collect(),
12003                            ),
12004                            ..Default::default()
12005                        },
12006                    })
12007                    .await
12008                    .into_response()
12009                    .unwrap();
12010                Ok(Some(json!(null)))
12011            }
12012        }
12013    });
12014
12015    cx.executor().start_waiting();
12016    editor
12017        .update_in(cx, |editor, window, cx| {
12018            editor.perform_format(
12019                project.clone(),
12020                FormatTrigger::Manual,
12021                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12022                window,
12023                cx,
12024            )
12025        })
12026        .unwrap()
12027        .await;
12028    editor.update(cx, |editor, cx| {
12029        assert_eq!(
12030            editor.text(cx),
12031            r#"
12032                applied-code-action-2-edit
12033                applied-code-action-1-command
12034                applied-code-action-1-edit
12035                applied-formatting
12036                one
12037                two
12038                three
12039            "#
12040            .unindent()
12041        );
12042    });
12043
12044    editor.update_in(cx, |editor, window, cx| {
12045        editor.undo(&Default::default(), window, cx);
12046        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12047    });
12048
12049    // Perform a manual edit while waiting for an LSP command
12050    // that's being run as part of a formatting code action.
12051    let lock_guard = command_lock.lock().await;
12052    let format = editor
12053        .update_in(cx, |editor, window, cx| {
12054            editor.perform_format(
12055                project.clone(),
12056                FormatTrigger::Manual,
12057                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12058                window,
12059                cx,
12060            )
12061        })
12062        .unwrap();
12063    cx.run_until_parked();
12064    editor.update(cx, |editor, cx| {
12065        assert_eq!(
12066            editor.text(cx),
12067            r#"
12068                applied-code-action-1-edit
12069                applied-formatting
12070                one
12071                two
12072                three
12073            "#
12074            .unindent()
12075        );
12076
12077        editor.buffer.update(cx, |buffer, cx| {
12078            let ix = buffer.len(cx);
12079            buffer.edit([(ix..ix, "edited\n")], None, cx);
12080        });
12081    });
12082
12083    // Allow the LSP command to proceed. Because the buffer was edited,
12084    // the second code action will not be run.
12085    drop(lock_guard);
12086    format.await;
12087    editor.update_in(cx, |editor, window, cx| {
12088        assert_eq!(
12089            editor.text(cx),
12090            r#"
12091                applied-code-action-1-command
12092                applied-code-action-1-edit
12093                applied-formatting
12094                one
12095                two
12096                three
12097                edited
12098            "#
12099            .unindent()
12100        );
12101
12102        // The manual edit is undone first, because it is the last thing the user did
12103        // (even though the command completed afterwards).
12104        editor.undo(&Default::default(), window, cx);
12105        assert_eq!(
12106            editor.text(cx),
12107            r#"
12108                applied-code-action-1-command
12109                applied-code-action-1-edit
12110                applied-formatting
12111                one
12112                two
12113                three
12114            "#
12115            .unindent()
12116        );
12117
12118        // All the formatting (including the command, which completed after the manual edit)
12119        // is undone together.
12120        editor.undo(&Default::default(), window, cx);
12121        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12122    });
12123}
12124
12125#[gpui::test]
12126async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12127    init_test(cx, |settings| {
12128        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
12129            Formatter::LanguageServer { name: None },
12130        ])))
12131    });
12132
12133    let fs = FakeFs::new(cx.executor());
12134    fs.insert_file(path!("/file.ts"), Default::default()).await;
12135
12136    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12137
12138    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12139    language_registry.add(Arc::new(Language::new(
12140        LanguageConfig {
12141            name: "TypeScript".into(),
12142            matcher: LanguageMatcher {
12143                path_suffixes: vec!["ts".to_string()],
12144                ..Default::default()
12145            },
12146            ..LanguageConfig::default()
12147        },
12148        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12149    )));
12150    update_test_language_settings(cx, |settings| {
12151        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12152    });
12153    let mut fake_servers = language_registry.register_fake_lsp(
12154        "TypeScript",
12155        FakeLspAdapter {
12156            capabilities: lsp::ServerCapabilities {
12157                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12158                ..Default::default()
12159            },
12160            ..Default::default()
12161        },
12162    );
12163
12164    let buffer = project
12165        .update(cx, |project, cx| {
12166            project.open_local_buffer(path!("/file.ts"), cx)
12167        })
12168        .await
12169        .unwrap();
12170
12171    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12172    let (editor, cx) = cx.add_window_view(|window, cx| {
12173        build_editor_with_project(project.clone(), buffer, window, cx)
12174    });
12175    editor.update_in(cx, |editor, window, cx| {
12176        editor.set_text(
12177            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12178            window,
12179            cx,
12180        )
12181    });
12182
12183    cx.executor().start_waiting();
12184    let fake_server = fake_servers.next().await.unwrap();
12185
12186    let format = editor
12187        .update_in(cx, |editor, window, cx| {
12188            editor.perform_code_action_kind(
12189                project.clone(),
12190                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12191                window,
12192                cx,
12193            )
12194        })
12195        .unwrap();
12196    fake_server
12197        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12198            assert_eq!(
12199                params.text_document.uri,
12200                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12201            );
12202            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12203                lsp::CodeAction {
12204                    title: "Organize Imports".to_string(),
12205                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12206                    edit: Some(lsp::WorkspaceEdit {
12207                        changes: Some(
12208                            [(
12209                                params.text_document.uri.clone(),
12210                                vec![lsp::TextEdit::new(
12211                                    lsp::Range::new(
12212                                        lsp::Position::new(1, 0),
12213                                        lsp::Position::new(2, 0),
12214                                    ),
12215                                    "".to_string(),
12216                                )],
12217                            )]
12218                            .into_iter()
12219                            .collect(),
12220                        ),
12221                        ..Default::default()
12222                    }),
12223                    ..Default::default()
12224                },
12225            )]))
12226        })
12227        .next()
12228        .await;
12229    cx.executor().start_waiting();
12230    format.await;
12231    assert_eq!(
12232        editor.update(cx, |editor, cx| editor.text(cx)),
12233        "import { a } from 'module';\n\nconst x = a;\n"
12234    );
12235
12236    editor.update_in(cx, |editor, window, cx| {
12237        editor.set_text(
12238            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12239            window,
12240            cx,
12241        )
12242    });
12243    // Ensure we don't lock if code action hangs.
12244    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12245        move |params, _| async move {
12246            assert_eq!(
12247                params.text_document.uri,
12248                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12249            );
12250            futures::future::pending::<()>().await;
12251            unreachable!()
12252        },
12253    );
12254    let format = editor
12255        .update_in(cx, |editor, window, cx| {
12256            editor.perform_code_action_kind(
12257                project,
12258                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12259                window,
12260                cx,
12261            )
12262        })
12263        .unwrap();
12264    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12265    cx.executor().start_waiting();
12266    format.await;
12267    assert_eq!(
12268        editor.update(cx, |editor, cx| editor.text(cx)),
12269        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12270    );
12271}
12272
12273#[gpui::test]
12274async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12275    init_test(cx, |_| {});
12276
12277    let mut cx = EditorLspTestContext::new_rust(
12278        lsp::ServerCapabilities {
12279            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12280            ..Default::default()
12281        },
12282        cx,
12283    )
12284    .await;
12285
12286    cx.set_state(indoc! {"
12287        one.twoˇ
12288    "});
12289
12290    // The format request takes a long time. When it completes, it inserts
12291    // a newline and an indent before the `.`
12292    cx.lsp
12293        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12294            let executor = cx.background_executor().clone();
12295            async move {
12296                executor.timer(Duration::from_millis(100)).await;
12297                Ok(Some(vec![lsp::TextEdit {
12298                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12299                    new_text: "\n    ".into(),
12300                }]))
12301            }
12302        });
12303
12304    // Submit a format request.
12305    let format_1 = cx
12306        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12307        .unwrap();
12308    cx.executor().run_until_parked();
12309
12310    // Submit a second format request.
12311    let format_2 = cx
12312        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12313        .unwrap();
12314    cx.executor().run_until_parked();
12315
12316    // Wait for both format requests to complete
12317    cx.executor().advance_clock(Duration::from_millis(200));
12318    cx.executor().start_waiting();
12319    format_1.await.unwrap();
12320    cx.executor().start_waiting();
12321    format_2.await.unwrap();
12322
12323    // The formatting edits only happens once.
12324    cx.assert_editor_state(indoc! {"
12325        one
12326            .twoˇ
12327    "});
12328}
12329
12330#[gpui::test]
12331async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12332    init_test(cx, |settings| {
12333        settings.defaults.formatter = Some(SelectedFormatter::Auto)
12334    });
12335
12336    let mut cx = EditorLspTestContext::new_rust(
12337        lsp::ServerCapabilities {
12338            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12339            ..Default::default()
12340        },
12341        cx,
12342    )
12343    .await;
12344
12345    // Set up a buffer white some trailing whitespace and no trailing newline.
12346    cx.set_state(
12347        &[
12348            "one ",   //
12349            "twoˇ",   //
12350            "three ", //
12351            "four",   //
12352        ]
12353        .join("\n"),
12354    );
12355
12356    // Submit a format request.
12357    let format = cx
12358        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12359        .unwrap();
12360
12361    // Record which buffer changes have been sent to the language server
12362    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12363    cx.lsp
12364        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12365            let buffer_changes = buffer_changes.clone();
12366            move |params, _| {
12367                buffer_changes.lock().extend(
12368                    params
12369                        .content_changes
12370                        .into_iter()
12371                        .map(|e| (e.range.unwrap(), e.text)),
12372                );
12373            }
12374        });
12375
12376    // Handle formatting requests to the language server.
12377    cx.lsp
12378        .set_request_handler::<lsp::request::Formatting, _, _>({
12379            let buffer_changes = buffer_changes.clone();
12380            move |_, _| {
12381                // When formatting is requested, trailing whitespace has already been stripped,
12382                // and the trailing newline has already been added.
12383                assert_eq!(
12384                    &buffer_changes.lock()[1..],
12385                    &[
12386                        (
12387                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12388                            "".into()
12389                        ),
12390                        (
12391                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12392                            "".into()
12393                        ),
12394                        (
12395                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12396                            "\n".into()
12397                        ),
12398                    ]
12399                );
12400
12401                // Insert blank lines between each line of the buffer.
12402                async move {
12403                    Ok(Some(vec![
12404                        lsp::TextEdit {
12405                            range: lsp::Range::new(
12406                                lsp::Position::new(1, 0),
12407                                lsp::Position::new(1, 0),
12408                            ),
12409                            new_text: "\n".into(),
12410                        },
12411                        lsp::TextEdit {
12412                            range: lsp::Range::new(
12413                                lsp::Position::new(2, 0),
12414                                lsp::Position::new(2, 0),
12415                            ),
12416                            new_text: "\n".into(),
12417                        },
12418                    ]))
12419                }
12420            }
12421        });
12422
12423    // After formatting the buffer, the trailing whitespace is stripped,
12424    // a newline is appended, and the edits provided by the language server
12425    // have been applied.
12426    format.await.unwrap();
12427    cx.assert_editor_state(
12428        &[
12429            "one",   //
12430            "",      //
12431            "twoˇ",  //
12432            "",      //
12433            "three", //
12434            "four",  //
12435            "",      //
12436        ]
12437        .join("\n"),
12438    );
12439
12440    // Undoing the formatting undoes the trailing whitespace removal, the
12441    // trailing newline, and the LSP edits.
12442    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12443    cx.assert_editor_state(
12444        &[
12445            "one ",   //
12446            "twoˇ",   //
12447            "three ", //
12448            "four",   //
12449        ]
12450        .join("\n"),
12451    );
12452}
12453
12454#[gpui::test]
12455async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12456    cx: &mut TestAppContext,
12457) {
12458    init_test(cx, |_| {});
12459
12460    cx.update(|cx| {
12461        cx.update_global::<SettingsStore, _>(|settings, cx| {
12462            settings.update_user_settings(cx, |settings| {
12463                settings.editor.auto_signature_help = Some(true);
12464            });
12465        });
12466    });
12467
12468    let mut cx = EditorLspTestContext::new_rust(
12469        lsp::ServerCapabilities {
12470            signature_help_provider: Some(lsp::SignatureHelpOptions {
12471                ..Default::default()
12472            }),
12473            ..Default::default()
12474        },
12475        cx,
12476    )
12477    .await;
12478
12479    let language = Language::new(
12480        LanguageConfig {
12481            name: "Rust".into(),
12482            brackets: BracketPairConfig {
12483                pairs: vec![
12484                    BracketPair {
12485                        start: "{".to_string(),
12486                        end: "}".to_string(),
12487                        close: true,
12488                        surround: true,
12489                        newline: true,
12490                    },
12491                    BracketPair {
12492                        start: "(".to_string(),
12493                        end: ")".to_string(),
12494                        close: true,
12495                        surround: true,
12496                        newline: true,
12497                    },
12498                    BracketPair {
12499                        start: "/*".to_string(),
12500                        end: " */".to_string(),
12501                        close: true,
12502                        surround: true,
12503                        newline: true,
12504                    },
12505                    BracketPair {
12506                        start: "[".to_string(),
12507                        end: "]".to_string(),
12508                        close: false,
12509                        surround: false,
12510                        newline: true,
12511                    },
12512                    BracketPair {
12513                        start: "\"".to_string(),
12514                        end: "\"".to_string(),
12515                        close: true,
12516                        surround: true,
12517                        newline: false,
12518                    },
12519                    BracketPair {
12520                        start: "<".to_string(),
12521                        end: ">".to_string(),
12522                        close: false,
12523                        surround: true,
12524                        newline: true,
12525                    },
12526                ],
12527                ..Default::default()
12528            },
12529            autoclose_before: "})]".to_string(),
12530            ..Default::default()
12531        },
12532        Some(tree_sitter_rust::LANGUAGE.into()),
12533    );
12534    let language = Arc::new(language);
12535
12536    cx.language_registry().add(language.clone());
12537    cx.update_buffer(|buffer, cx| {
12538        buffer.set_language(Some(language), cx);
12539    });
12540
12541    cx.set_state(
12542        &r#"
12543            fn main() {
12544                sampleˇ
12545            }
12546        "#
12547        .unindent(),
12548    );
12549
12550    cx.update_editor(|editor, window, cx| {
12551        editor.handle_input("(", window, cx);
12552    });
12553    cx.assert_editor_state(
12554        &"
12555            fn main() {
12556                sample(ˇ)
12557            }
12558        "
12559        .unindent(),
12560    );
12561
12562    let mocked_response = lsp::SignatureHelp {
12563        signatures: vec![lsp::SignatureInformation {
12564            label: "fn sample(param1: u8, param2: u8)".to_string(),
12565            documentation: None,
12566            parameters: Some(vec![
12567                lsp::ParameterInformation {
12568                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12569                    documentation: None,
12570                },
12571                lsp::ParameterInformation {
12572                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12573                    documentation: None,
12574                },
12575            ]),
12576            active_parameter: None,
12577        }],
12578        active_signature: Some(0),
12579        active_parameter: Some(0),
12580    };
12581    handle_signature_help_request(&mut cx, mocked_response).await;
12582
12583    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12584        .await;
12585
12586    cx.editor(|editor, _, _| {
12587        let signature_help_state = editor.signature_help_state.popover().cloned();
12588        let signature = signature_help_state.unwrap();
12589        assert_eq!(
12590            signature.signatures[signature.current_signature].label,
12591            "fn sample(param1: u8, param2: u8)"
12592        );
12593    });
12594}
12595
12596#[gpui::test]
12597async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12598    init_test(cx, |_| {});
12599
12600    cx.update(|cx| {
12601        cx.update_global::<SettingsStore, _>(|settings, cx| {
12602            settings.update_user_settings(cx, |settings| {
12603                settings.editor.auto_signature_help = Some(false);
12604                settings.editor.show_signature_help_after_edits = Some(false);
12605            });
12606        });
12607    });
12608
12609    let mut cx = EditorLspTestContext::new_rust(
12610        lsp::ServerCapabilities {
12611            signature_help_provider: Some(lsp::SignatureHelpOptions {
12612                ..Default::default()
12613            }),
12614            ..Default::default()
12615        },
12616        cx,
12617    )
12618    .await;
12619
12620    let language = Language::new(
12621        LanguageConfig {
12622            name: "Rust".into(),
12623            brackets: BracketPairConfig {
12624                pairs: vec![
12625                    BracketPair {
12626                        start: "{".to_string(),
12627                        end: "}".to_string(),
12628                        close: true,
12629                        surround: true,
12630                        newline: true,
12631                    },
12632                    BracketPair {
12633                        start: "(".to_string(),
12634                        end: ")".to_string(),
12635                        close: true,
12636                        surround: true,
12637                        newline: true,
12638                    },
12639                    BracketPair {
12640                        start: "/*".to_string(),
12641                        end: " */".to_string(),
12642                        close: true,
12643                        surround: true,
12644                        newline: true,
12645                    },
12646                    BracketPair {
12647                        start: "[".to_string(),
12648                        end: "]".to_string(),
12649                        close: false,
12650                        surround: false,
12651                        newline: true,
12652                    },
12653                    BracketPair {
12654                        start: "\"".to_string(),
12655                        end: "\"".to_string(),
12656                        close: true,
12657                        surround: true,
12658                        newline: false,
12659                    },
12660                    BracketPair {
12661                        start: "<".to_string(),
12662                        end: ">".to_string(),
12663                        close: false,
12664                        surround: true,
12665                        newline: true,
12666                    },
12667                ],
12668                ..Default::default()
12669            },
12670            autoclose_before: "})]".to_string(),
12671            ..Default::default()
12672        },
12673        Some(tree_sitter_rust::LANGUAGE.into()),
12674    );
12675    let language = Arc::new(language);
12676
12677    cx.language_registry().add(language.clone());
12678    cx.update_buffer(|buffer, cx| {
12679        buffer.set_language(Some(language), cx);
12680    });
12681
12682    // Ensure that signature_help is not called when no signature help is enabled.
12683    cx.set_state(
12684        &r#"
12685            fn main() {
12686                sampleˇ
12687            }
12688        "#
12689        .unindent(),
12690    );
12691    cx.update_editor(|editor, window, cx| {
12692        editor.handle_input("(", window, cx);
12693    });
12694    cx.assert_editor_state(
12695        &"
12696            fn main() {
12697                sample(ˇ)
12698            }
12699        "
12700        .unindent(),
12701    );
12702    cx.editor(|editor, _, _| {
12703        assert!(editor.signature_help_state.task().is_none());
12704    });
12705
12706    let mocked_response = lsp::SignatureHelp {
12707        signatures: vec![lsp::SignatureInformation {
12708            label: "fn sample(param1: u8, param2: u8)".to_string(),
12709            documentation: None,
12710            parameters: Some(vec![
12711                lsp::ParameterInformation {
12712                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12713                    documentation: None,
12714                },
12715                lsp::ParameterInformation {
12716                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12717                    documentation: None,
12718                },
12719            ]),
12720            active_parameter: None,
12721        }],
12722        active_signature: Some(0),
12723        active_parameter: Some(0),
12724    };
12725
12726    // Ensure that signature_help is called when enabled afte edits
12727    cx.update(|_, cx| {
12728        cx.update_global::<SettingsStore, _>(|settings, cx| {
12729            settings.update_user_settings(cx, |settings| {
12730                settings.editor.auto_signature_help = Some(false);
12731                settings.editor.show_signature_help_after_edits = Some(true);
12732            });
12733        });
12734    });
12735    cx.set_state(
12736        &r#"
12737            fn main() {
12738                sampleˇ
12739            }
12740        "#
12741        .unindent(),
12742    );
12743    cx.update_editor(|editor, window, cx| {
12744        editor.handle_input("(", window, cx);
12745    });
12746    cx.assert_editor_state(
12747        &"
12748            fn main() {
12749                sample(ˇ)
12750            }
12751        "
12752        .unindent(),
12753    );
12754    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12755    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12756        .await;
12757    cx.update_editor(|editor, _, _| {
12758        let signature_help_state = editor.signature_help_state.popover().cloned();
12759        assert!(signature_help_state.is_some());
12760        let signature = signature_help_state.unwrap();
12761        assert_eq!(
12762            signature.signatures[signature.current_signature].label,
12763            "fn sample(param1: u8, param2: u8)"
12764        );
12765        editor.signature_help_state = SignatureHelpState::default();
12766    });
12767
12768    // Ensure that signature_help is called when auto signature help override is enabled
12769    cx.update(|_, cx| {
12770        cx.update_global::<SettingsStore, _>(|settings, cx| {
12771            settings.update_user_settings(cx, |settings| {
12772                settings.editor.auto_signature_help = Some(true);
12773                settings.editor.show_signature_help_after_edits = Some(false);
12774            });
12775        });
12776    });
12777    cx.set_state(
12778        &r#"
12779            fn main() {
12780                sampleˇ
12781            }
12782        "#
12783        .unindent(),
12784    );
12785    cx.update_editor(|editor, window, cx| {
12786        editor.handle_input("(", window, cx);
12787    });
12788    cx.assert_editor_state(
12789        &"
12790            fn main() {
12791                sample(ˇ)
12792            }
12793        "
12794        .unindent(),
12795    );
12796    handle_signature_help_request(&mut cx, mocked_response).await;
12797    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12798        .await;
12799    cx.editor(|editor, _, _| {
12800        let signature_help_state = editor.signature_help_state.popover().cloned();
12801        assert!(signature_help_state.is_some());
12802        let signature = signature_help_state.unwrap();
12803        assert_eq!(
12804            signature.signatures[signature.current_signature].label,
12805            "fn sample(param1: u8, param2: u8)"
12806        );
12807    });
12808}
12809
12810#[gpui::test]
12811async fn test_signature_help(cx: &mut TestAppContext) {
12812    init_test(cx, |_| {});
12813    cx.update(|cx| {
12814        cx.update_global::<SettingsStore, _>(|settings, cx| {
12815            settings.update_user_settings(cx, |settings| {
12816                settings.editor.auto_signature_help = Some(true);
12817            });
12818        });
12819    });
12820
12821    let mut cx = EditorLspTestContext::new_rust(
12822        lsp::ServerCapabilities {
12823            signature_help_provider: Some(lsp::SignatureHelpOptions {
12824                ..Default::default()
12825            }),
12826            ..Default::default()
12827        },
12828        cx,
12829    )
12830    .await;
12831
12832    // A test that directly calls `show_signature_help`
12833    cx.update_editor(|editor, window, cx| {
12834        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12835    });
12836
12837    let mocked_response = lsp::SignatureHelp {
12838        signatures: vec![lsp::SignatureInformation {
12839            label: "fn sample(param1: u8, param2: u8)".to_string(),
12840            documentation: None,
12841            parameters: Some(vec![
12842                lsp::ParameterInformation {
12843                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12844                    documentation: None,
12845                },
12846                lsp::ParameterInformation {
12847                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12848                    documentation: None,
12849                },
12850            ]),
12851            active_parameter: None,
12852        }],
12853        active_signature: Some(0),
12854        active_parameter: Some(0),
12855    };
12856    handle_signature_help_request(&mut cx, mocked_response).await;
12857
12858    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12859        .await;
12860
12861    cx.editor(|editor, _, _| {
12862        let signature_help_state = editor.signature_help_state.popover().cloned();
12863        assert!(signature_help_state.is_some());
12864        let signature = signature_help_state.unwrap();
12865        assert_eq!(
12866            signature.signatures[signature.current_signature].label,
12867            "fn sample(param1: u8, param2: u8)"
12868        );
12869    });
12870
12871    // When exiting outside from inside the brackets, `signature_help` is closed.
12872    cx.set_state(indoc! {"
12873        fn main() {
12874            sample(ˇ);
12875        }
12876
12877        fn sample(param1: u8, param2: u8) {}
12878    "});
12879
12880    cx.update_editor(|editor, window, cx| {
12881        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12882            s.select_ranges([0..0])
12883        });
12884    });
12885
12886    let mocked_response = lsp::SignatureHelp {
12887        signatures: Vec::new(),
12888        active_signature: None,
12889        active_parameter: None,
12890    };
12891    handle_signature_help_request(&mut cx, mocked_response).await;
12892
12893    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12894        .await;
12895
12896    cx.editor(|editor, _, _| {
12897        assert!(!editor.signature_help_state.is_shown());
12898    });
12899
12900    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
12901    cx.set_state(indoc! {"
12902        fn main() {
12903            sample(ˇ);
12904        }
12905
12906        fn sample(param1: u8, param2: u8) {}
12907    "});
12908
12909    let mocked_response = lsp::SignatureHelp {
12910        signatures: vec![lsp::SignatureInformation {
12911            label: "fn sample(param1: u8, param2: u8)".to_string(),
12912            documentation: None,
12913            parameters: Some(vec![
12914                lsp::ParameterInformation {
12915                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12916                    documentation: None,
12917                },
12918                lsp::ParameterInformation {
12919                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12920                    documentation: None,
12921                },
12922            ]),
12923            active_parameter: None,
12924        }],
12925        active_signature: Some(0),
12926        active_parameter: Some(0),
12927    };
12928    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12929    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12930        .await;
12931    cx.editor(|editor, _, _| {
12932        assert!(editor.signature_help_state.is_shown());
12933    });
12934
12935    // Restore the popover with more parameter input
12936    cx.set_state(indoc! {"
12937        fn main() {
12938            sample(param1, param2ˇ);
12939        }
12940
12941        fn sample(param1: u8, param2: u8) {}
12942    "});
12943
12944    let mocked_response = lsp::SignatureHelp {
12945        signatures: vec![lsp::SignatureInformation {
12946            label: "fn sample(param1: u8, param2: u8)".to_string(),
12947            documentation: None,
12948            parameters: Some(vec![
12949                lsp::ParameterInformation {
12950                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12951                    documentation: None,
12952                },
12953                lsp::ParameterInformation {
12954                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12955                    documentation: None,
12956                },
12957            ]),
12958            active_parameter: None,
12959        }],
12960        active_signature: Some(0),
12961        active_parameter: Some(1),
12962    };
12963    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12964    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12965        .await;
12966
12967    // When selecting a range, the popover is gone.
12968    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
12969    cx.update_editor(|editor, window, cx| {
12970        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12971            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12972        })
12973    });
12974    cx.assert_editor_state(indoc! {"
12975        fn main() {
12976            sample(param1, «ˇparam2»);
12977        }
12978
12979        fn sample(param1: u8, param2: u8) {}
12980    "});
12981    cx.editor(|editor, _, _| {
12982        assert!(!editor.signature_help_state.is_shown());
12983    });
12984
12985    // When unselecting again, the popover is back if within the brackets.
12986    cx.update_editor(|editor, window, cx| {
12987        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12988            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12989        })
12990    });
12991    cx.assert_editor_state(indoc! {"
12992        fn main() {
12993            sample(param1, ˇparam2);
12994        }
12995
12996        fn sample(param1: u8, param2: u8) {}
12997    "});
12998    handle_signature_help_request(&mut cx, mocked_response).await;
12999    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13000        .await;
13001    cx.editor(|editor, _, _| {
13002        assert!(editor.signature_help_state.is_shown());
13003    });
13004
13005    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13006    cx.update_editor(|editor, window, cx| {
13007        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13008            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13009            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13010        })
13011    });
13012    cx.assert_editor_state(indoc! {"
13013        fn main() {
13014            sample(param1, ˇparam2);
13015        }
13016
13017        fn sample(param1: u8, param2: u8) {}
13018    "});
13019
13020    let mocked_response = lsp::SignatureHelp {
13021        signatures: vec![lsp::SignatureInformation {
13022            label: "fn sample(param1: u8, param2: u8)".to_string(),
13023            documentation: None,
13024            parameters: Some(vec![
13025                lsp::ParameterInformation {
13026                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13027                    documentation: None,
13028                },
13029                lsp::ParameterInformation {
13030                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13031                    documentation: None,
13032                },
13033            ]),
13034            active_parameter: None,
13035        }],
13036        active_signature: Some(0),
13037        active_parameter: Some(1),
13038    };
13039    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13040    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13041        .await;
13042    cx.update_editor(|editor, _, cx| {
13043        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13044    });
13045    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13046        .await;
13047    cx.update_editor(|editor, window, cx| {
13048        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13049            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13050        })
13051    });
13052    cx.assert_editor_state(indoc! {"
13053        fn main() {
13054            sample(param1, «ˇparam2»);
13055        }
13056
13057        fn sample(param1: u8, param2: u8) {}
13058    "});
13059    cx.update_editor(|editor, window, cx| {
13060        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13061            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13062        })
13063    });
13064    cx.assert_editor_state(indoc! {"
13065        fn main() {
13066            sample(param1, ˇparam2);
13067        }
13068
13069        fn sample(param1: u8, param2: u8) {}
13070    "});
13071    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13072        .await;
13073}
13074
13075#[gpui::test]
13076async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13077    init_test(cx, |_| {});
13078
13079    let mut cx = EditorLspTestContext::new_rust(
13080        lsp::ServerCapabilities {
13081            signature_help_provider: Some(lsp::SignatureHelpOptions {
13082                ..Default::default()
13083            }),
13084            ..Default::default()
13085        },
13086        cx,
13087    )
13088    .await;
13089
13090    cx.set_state(indoc! {"
13091        fn main() {
13092            overloadedˇ
13093        }
13094    "});
13095
13096    cx.update_editor(|editor, window, cx| {
13097        editor.handle_input("(", window, cx);
13098        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13099    });
13100
13101    // Mock response with 3 signatures
13102    let mocked_response = lsp::SignatureHelp {
13103        signatures: vec![
13104            lsp::SignatureInformation {
13105                label: "fn overloaded(x: i32)".to_string(),
13106                documentation: None,
13107                parameters: Some(vec![lsp::ParameterInformation {
13108                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13109                    documentation: None,
13110                }]),
13111                active_parameter: None,
13112            },
13113            lsp::SignatureInformation {
13114                label: "fn overloaded(x: i32, y: i32)".to_string(),
13115                documentation: None,
13116                parameters: Some(vec![
13117                    lsp::ParameterInformation {
13118                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13119                        documentation: None,
13120                    },
13121                    lsp::ParameterInformation {
13122                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13123                        documentation: None,
13124                    },
13125                ]),
13126                active_parameter: None,
13127            },
13128            lsp::SignatureInformation {
13129                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13130                documentation: None,
13131                parameters: Some(vec![
13132                    lsp::ParameterInformation {
13133                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13134                        documentation: None,
13135                    },
13136                    lsp::ParameterInformation {
13137                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13138                        documentation: None,
13139                    },
13140                    lsp::ParameterInformation {
13141                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13142                        documentation: None,
13143                    },
13144                ]),
13145                active_parameter: None,
13146            },
13147        ],
13148        active_signature: Some(1),
13149        active_parameter: Some(0),
13150    };
13151    handle_signature_help_request(&mut cx, mocked_response).await;
13152
13153    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13154        .await;
13155
13156    // Verify we have multiple signatures and the right one is selected
13157    cx.editor(|editor, _, _| {
13158        let popover = editor.signature_help_state.popover().cloned().unwrap();
13159        assert_eq!(popover.signatures.len(), 3);
13160        // active_signature was 1, so that should be the current
13161        assert_eq!(popover.current_signature, 1);
13162        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13163        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13164        assert_eq!(
13165            popover.signatures[2].label,
13166            "fn overloaded(x: i32, y: i32, z: i32)"
13167        );
13168    });
13169
13170    // Test navigation functionality
13171    cx.update_editor(|editor, window, cx| {
13172        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13173    });
13174
13175    cx.editor(|editor, _, _| {
13176        let popover = editor.signature_help_state.popover().cloned().unwrap();
13177        assert_eq!(popover.current_signature, 2);
13178    });
13179
13180    // Test wrap around
13181    cx.update_editor(|editor, window, cx| {
13182        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13183    });
13184
13185    cx.editor(|editor, _, _| {
13186        let popover = editor.signature_help_state.popover().cloned().unwrap();
13187        assert_eq!(popover.current_signature, 0);
13188    });
13189
13190    // Test previous navigation
13191    cx.update_editor(|editor, window, cx| {
13192        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13193    });
13194
13195    cx.editor(|editor, _, _| {
13196        let popover = editor.signature_help_state.popover().cloned().unwrap();
13197        assert_eq!(popover.current_signature, 2);
13198    });
13199}
13200
13201#[gpui::test]
13202async fn test_completion_mode(cx: &mut TestAppContext) {
13203    init_test(cx, |_| {});
13204    let mut cx = EditorLspTestContext::new_rust(
13205        lsp::ServerCapabilities {
13206            completion_provider: Some(lsp::CompletionOptions {
13207                resolve_provider: Some(true),
13208                ..Default::default()
13209            }),
13210            ..Default::default()
13211        },
13212        cx,
13213    )
13214    .await;
13215
13216    struct Run {
13217        run_description: &'static str,
13218        initial_state: String,
13219        buffer_marked_text: String,
13220        completion_label: &'static str,
13221        completion_text: &'static str,
13222        expected_with_insert_mode: String,
13223        expected_with_replace_mode: String,
13224        expected_with_replace_subsequence_mode: String,
13225        expected_with_replace_suffix_mode: String,
13226    }
13227
13228    let runs = [
13229        Run {
13230            run_description: "Start of word matches completion text",
13231            initial_state: "before ediˇ after".into(),
13232            buffer_marked_text: "before <edi|> after".into(),
13233            completion_label: "editor",
13234            completion_text: "editor",
13235            expected_with_insert_mode: "before editorˇ after".into(),
13236            expected_with_replace_mode: "before editorˇ after".into(),
13237            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13238            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13239        },
13240        Run {
13241            run_description: "Accept same text at the middle of the word",
13242            initial_state: "before ediˇtor after".into(),
13243            buffer_marked_text: "before <edi|tor> after".into(),
13244            completion_label: "editor",
13245            completion_text: "editor",
13246            expected_with_insert_mode: "before editorˇtor after".into(),
13247            expected_with_replace_mode: "before editorˇ after".into(),
13248            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13249            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13250        },
13251        Run {
13252            run_description: "End of word matches completion text -- cursor at end",
13253            initial_state: "before torˇ after".into(),
13254            buffer_marked_text: "before <tor|> after".into(),
13255            completion_label: "editor",
13256            completion_text: "editor",
13257            expected_with_insert_mode: "before editorˇ after".into(),
13258            expected_with_replace_mode: "before editorˇ after".into(),
13259            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13260            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13261        },
13262        Run {
13263            run_description: "End of word matches completion text -- cursor at start",
13264            initial_state: "before ˇtor after".into(),
13265            buffer_marked_text: "before <|tor> after".into(),
13266            completion_label: "editor",
13267            completion_text: "editor",
13268            expected_with_insert_mode: "before editorˇtor after".into(),
13269            expected_with_replace_mode: "before editorˇ after".into(),
13270            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13271            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13272        },
13273        Run {
13274            run_description: "Prepend text containing whitespace",
13275            initial_state: "pˇfield: bool".into(),
13276            buffer_marked_text: "<p|field>: bool".into(),
13277            completion_label: "pub ",
13278            completion_text: "pub ",
13279            expected_with_insert_mode: "pub ˇfield: bool".into(),
13280            expected_with_replace_mode: "pub ˇ: bool".into(),
13281            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13282            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13283        },
13284        Run {
13285            run_description: "Add element to start of list",
13286            initial_state: "[element_ˇelement_2]".into(),
13287            buffer_marked_text: "[<element_|element_2>]".into(),
13288            completion_label: "element_1",
13289            completion_text: "element_1",
13290            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13291            expected_with_replace_mode: "[element_1ˇ]".into(),
13292            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13293            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13294        },
13295        Run {
13296            run_description: "Add element to start of list -- first and second elements are equal",
13297            initial_state: "[elˇelement]".into(),
13298            buffer_marked_text: "[<el|element>]".into(),
13299            completion_label: "element",
13300            completion_text: "element",
13301            expected_with_insert_mode: "[elementˇelement]".into(),
13302            expected_with_replace_mode: "[elementˇ]".into(),
13303            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13304            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13305        },
13306        Run {
13307            run_description: "Ends with matching suffix",
13308            initial_state: "SubˇError".into(),
13309            buffer_marked_text: "<Sub|Error>".into(),
13310            completion_label: "SubscriptionError",
13311            completion_text: "SubscriptionError",
13312            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13313            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13314            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13315            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13316        },
13317        Run {
13318            run_description: "Suffix is a subsequence -- contiguous",
13319            initial_state: "SubˇErr".into(),
13320            buffer_marked_text: "<Sub|Err>".into(),
13321            completion_label: "SubscriptionError",
13322            completion_text: "SubscriptionError",
13323            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13324            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13325            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13326            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13327        },
13328        Run {
13329            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13330            initial_state: "Suˇscrirr".into(),
13331            buffer_marked_text: "<Su|scrirr>".into(),
13332            completion_label: "SubscriptionError",
13333            completion_text: "SubscriptionError",
13334            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13335            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13336            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13337            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13338        },
13339        Run {
13340            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13341            initial_state: "foo(indˇix)".into(),
13342            buffer_marked_text: "foo(<ind|ix>)".into(),
13343            completion_label: "node_index",
13344            completion_text: "node_index",
13345            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13346            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13347            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13348            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13349        },
13350        Run {
13351            run_description: "Replace range ends before cursor - should extend to cursor",
13352            initial_state: "before editˇo after".into(),
13353            buffer_marked_text: "before <{ed}>it|o after".into(),
13354            completion_label: "editor",
13355            completion_text: "editor",
13356            expected_with_insert_mode: "before editorˇo after".into(),
13357            expected_with_replace_mode: "before editorˇo after".into(),
13358            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13359            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13360        },
13361        Run {
13362            run_description: "Uses label for suffix matching",
13363            initial_state: "before ediˇtor after".into(),
13364            buffer_marked_text: "before <edi|tor> after".into(),
13365            completion_label: "editor",
13366            completion_text: "editor()",
13367            expected_with_insert_mode: "before editor()ˇtor after".into(),
13368            expected_with_replace_mode: "before editor()ˇ after".into(),
13369            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13370            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13371        },
13372        Run {
13373            run_description: "Case insensitive subsequence and suffix matching",
13374            initial_state: "before EDiˇtoR after".into(),
13375            buffer_marked_text: "before <EDi|toR> after".into(),
13376            completion_label: "editor",
13377            completion_text: "editor",
13378            expected_with_insert_mode: "before editorˇtoR after".into(),
13379            expected_with_replace_mode: "before editorˇ after".into(),
13380            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13381            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13382        },
13383    ];
13384
13385    for run in runs {
13386        let run_variations = [
13387            (LspInsertMode::Insert, run.expected_with_insert_mode),
13388            (LspInsertMode::Replace, run.expected_with_replace_mode),
13389            (
13390                LspInsertMode::ReplaceSubsequence,
13391                run.expected_with_replace_subsequence_mode,
13392            ),
13393            (
13394                LspInsertMode::ReplaceSuffix,
13395                run.expected_with_replace_suffix_mode,
13396            ),
13397        ];
13398
13399        for (lsp_insert_mode, expected_text) in run_variations {
13400            eprintln!(
13401                "run = {:?}, mode = {lsp_insert_mode:.?}",
13402                run.run_description,
13403            );
13404
13405            update_test_language_settings(&mut cx, |settings| {
13406                settings.defaults.completions = Some(CompletionSettingsContent {
13407                    lsp_insert_mode: Some(lsp_insert_mode),
13408                    words: Some(WordsCompletionMode::Disabled),
13409                    words_min_length: Some(0),
13410                    ..Default::default()
13411                });
13412            });
13413
13414            cx.set_state(&run.initial_state);
13415            cx.update_editor(|editor, window, cx| {
13416                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13417            });
13418
13419            let counter = Arc::new(AtomicUsize::new(0));
13420            handle_completion_request_with_insert_and_replace(
13421                &mut cx,
13422                &run.buffer_marked_text,
13423                vec![(run.completion_label, run.completion_text)],
13424                counter.clone(),
13425            )
13426            .await;
13427            cx.condition(|editor, _| editor.context_menu_visible())
13428                .await;
13429            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13430
13431            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13432                editor
13433                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13434                    .unwrap()
13435            });
13436            cx.assert_editor_state(&expected_text);
13437            handle_resolve_completion_request(&mut cx, None).await;
13438            apply_additional_edits.await.unwrap();
13439        }
13440    }
13441}
13442
13443#[gpui::test]
13444async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13445    init_test(cx, |_| {});
13446    let mut cx = EditorLspTestContext::new_rust(
13447        lsp::ServerCapabilities {
13448            completion_provider: Some(lsp::CompletionOptions {
13449                resolve_provider: Some(true),
13450                ..Default::default()
13451            }),
13452            ..Default::default()
13453        },
13454        cx,
13455    )
13456    .await;
13457
13458    let initial_state = "SubˇError";
13459    let buffer_marked_text = "<Sub|Error>";
13460    let completion_text = "SubscriptionError";
13461    let expected_with_insert_mode = "SubscriptionErrorˇError";
13462    let expected_with_replace_mode = "SubscriptionErrorˇ";
13463
13464    update_test_language_settings(&mut cx, |settings| {
13465        settings.defaults.completions = Some(CompletionSettingsContent {
13466            words: Some(WordsCompletionMode::Disabled),
13467            words_min_length: Some(0),
13468            // set the opposite here to ensure that the action is overriding the default behavior
13469            lsp_insert_mode: Some(LspInsertMode::Insert),
13470            ..Default::default()
13471        });
13472    });
13473
13474    cx.set_state(initial_state);
13475    cx.update_editor(|editor, window, cx| {
13476        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13477    });
13478
13479    let counter = Arc::new(AtomicUsize::new(0));
13480    handle_completion_request_with_insert_and_replace(
13481        &mut cx,
13482        buffer_marked_text,
13483        vec![(completion_text, completion_text)],
13484        counter.clone(),
13485    )
13486    .await;
13487    cx.condition(|editor, _| editor.context_menu_visible())
13488        .await;
13489    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13490
13491    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13492        editor
13493            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13494            .unwrap()
13495    });
13496    cx.assert_editor_state(expected_with_replace_mode);
13497    handle_resolve_completion_request(&mut cx, None).await;
13498    apply_additional_edits.await.unwrap();
13499
13500    update_test_language_settings(&mut cx, |settings| {
13501        settings.defaults.completions = Some(CompletionSettingsContent {
13502            words: Some(WordsCompletionMode::Disabled),
13503            words_min_length: Some(0),
13504            // set the opposite here to ensure that the action is overriding the default behavior
13505            lsp_insert_mode: Some(LspInsertMode::Replace),
13506            ..Default::default()
13507        });
13508    });
13509
13510    cx.set_state(initial_state);
13511    cx.update_editor(|editor, window, cx| {
13512        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13513    });
13514    handle_completion_request_with_insert_and_replace(
13515        &mut cx,
13516        buffer_marked_text,
13517        vec![(completion_text, completion_text)],
13518        counter.clone(),
13519    )
13520    .await;
13521    cx.condition(|editor, _| editor.context_menu_visible())
13522        .await;
13523    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13524
13525    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13526        editor
13527            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13528            .unwrap()
13529    });
13530    cx.assert_editor_state(expected_with_insert_mode);
13531    handle_resolve_completion_request(&mut cx, None).await;
13532    apply_additional_edits.await.unwrap();
13533}
13534
13535#[gpui::test]
13536async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13537    init_test(cx, |_| {});
13538    let mut cx = EditorLspTestContext::new_rust(
13539        lsp::ServerCapabilities {
13540            completion_provider: Some(lsp::CompletionOptions {
13541                resolve_provider: Some(true),
13542                ..Default::default()
13543            }),
13544            ..Default::default()
13545        },
13546        cx,
13547    )
13548    .await;
13549
13550    // scenario: surrounding text matches completion text
13551    let completion_text = "to_offset";
13552    let initial_state = indoc! {"
13553        1. buf.to_offˇsuffix
13554        2. buf.to_offˇsuf
13555        3. buf.to_offˇfix
13556        4. buf.to_offˇ
13557        5. into_offˇensive
13558        6. ˇsuffix
13559        7. let ˇ //
13560        8. aaˇzz
13561        9. buf.to_off«zzzzzˇ»suffix
13562        10. buf.«ˇzzzzz»suffix
13563        11. to_off«ˇzzzzz»
13564
13565        buf.to_offˇsuffix  // newest cursor
13566    "};
13567    let completion_marked_buffer = indoc! {"
13568        1. buf.to_offsuffix
13569        2. buf.to_offsuf
13570        3. buf.to_offfix
13571        4. buf.to_off
13572        5. into_offensive
13573        6. suffix
13574        7. let  //
13575        8. aazz
13576        9. buf.to_offzzzzzsuffix
13577        10. buf.zzzzzsuffix
13578        11. to_offzzzzz
13579
13580        buf.<to_off|suffix>  // newest cursor
13581    "};
13582    let expected = indoc! {"
13583        1. buf.to_offsetˇ
13584        2. buf.to_offsetˇsuf
13585        3. buf.to_offsetˇfix
13586        4. buf.to_offsetˇ
13587        5. into_offsetˇensive
13588        6. to_offsetˇsuffix
13589        7. let to_offsetˇ //
13590        8. aato_offsetˇzz
13591        9. buf.to_offsetˇ
13592        10. buf.to_offsetˇsuffix
13593        11. to_offsetˇ
13594
13595        buf.to_offsetˇ  // newest cursor
13596    "};
13597    cx.set_state(initial_state);
13598    cx.update_editor(|editor, window, cx| {
13599        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13600    });
13601    handle_completion_request_with_insert_and_replace(
13602        &mut cx,
13603        completion_marked_buffer,
13604        vec![(completion_text, completion_text)],
13605        Arc::new(AtomicUsize::new(0)),
13606    )
13607    .await;
13608    cx.condition(|editor, _| editor.context_menu_visible())
13609        .await;
13610    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13611        editor
13612            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13613            .unwrap()
13614    });
13615    cx.assert_editor_state(expected);
13616    handle_resolve_completion_request(&mut cx, None).await;
13617    apply_additional_edits.await.unwrap();
13618
13619    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13620    let completion_text = "foo_and_bar";
13621    let initial_state = indoc! {"
13622        1. ooanbˇ
13623        2. zooanbˇ
13624        3. ooanbˇz
13625        4. zooanbˇz
13626        5. ooanˇ
13627        6. oanbˇ
13628
13629        ooanbˇ
13630    "};
13631    let completion_marked_buffer = indoc! {"
13632        1. ooanb
13633        2. zooanb
13634        3. ooanbz
13635        4. zooanbz
13636        5. ooan
13637        6. oanb
13638
13639        <ooanb|>
13640    "};
13641    let expected = indoc! {"
13642        1. foo_and_barˇ
13643        2. zfoo_and_barˇ
13644        3. foo_and_barˇz
13645        4. zfoo_and_barˇz
13646        5. ooanfoo_and_barˇ
13647        6. oanbfoo_and_barˇ
13648
13649        foo_and_barˇ
13650    "};
13651    cx.set_state(initial_state);
13652    cx.update_editor(|editor, window, cx| {
13653        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13654    });
13655    handle_completion_request_with_insert_and_replace(
13656        &mut cx,
13657        completion_marked_buffer,
13658        vec![(completion_text, completion_text)],
13659        Arc::new(AtomicUsize::new(0)),
13660    )
13661    .await;
13662    cx.condition(|editor, _| editor.context_menu_visible())
13663        .await;
13664    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13665        editor
13666            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13667            .unwrap()
13668    });
13669    cx.assert_editor_state(expected);
13670    handle_resolve_completion_request(&mut cx, None).await;
13671    apply_additional_edits.await.unwrap();
13672
13673    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13674    // (expects the same as if it was inserted at the end)
13675    let completion_text = "foo_and_bar";
13676    let initial_state = indoc! {"
13677        1. ooˇanb
13678        2. zooˇanb
13679        3. ooˇanbz
13680        4. zooˇanbz
13681
13682        ooˇanb
13683    "};
13684    let completion_marked_buffer = indoc! {"
13685        1. ooanb
13686        2. zooanb
13687        3. ooanbz
13688        4. zooanbz
13689
13690        <oo|anb>
13691    "};
13692    let expected = indoc! {"
13693        1. foo_and_barˇ
13694        2. zfoo_and_barˇ
13695        3. foo_and_barˇz
13696        4. zfoo_and_barˇz
13697
13698        foo_and_barˇ
13699    "};
13700    cx.set_state(initial_state);
13701    cx.update_editor(|editor, window, cx| {
13702        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13703    });
13704    handle_completion_request_with_insert_and_replace(
13705        &mut cx,
13706        completion_marked_buffer,
13707        vec![(completion_text, completion_text)],
13708        Arc::new(AtomicUsize::new(0)),
13709    )
13710    .await;
13711    cx.condition(|editor, _| editor.context_menu_visible())
13712        .await;
13713    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13714        editor
13715            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13716            .unwrap()
13717    });
13718    cx.assert_editor_state(expected);
13719    handle_resolve_completion_request(&mut cx, None).await;
13720    apply_additional_edits.await.unwrap();
13721}
13722
13723// This used to crash
13724#[gpui::test]
13725async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13726    init_test(cx, |_| {});
13727
13728    let buffer_text = indoc! {"
13729        fn main() {
13730            10.satu;
13731
13732            //
13733            // separate cursors so they open in different excerpts (manually reproducible)
13734            //
13735
13736            10.satu20;
13737        }
13738    "};
13739    let multibuffer_text_with_selections = indoc! {"
13740        fn main() {
13741            10.satuˇ;
13742
13743            //
13744
13745            //
13746
13747            10.satuˇ20;
13748        }
13749    "};
13750    let expected_multibuffer = indoc! {"
13751        fn main() {
13752            10.saturating_sub()ˇ;
13753
13754            //
13755
13756            //
13757
13758            10.saturating_sub()ˇ;
13759        }
13760    "};
13761
13762    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13763    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13764
13765    let fs = FakeFs::new(cx.executor());
13766    fs.insert_tree(
13767        path!("/a"),
13768        json!({
13769            "main.rs": buffer_text,
13770        }),
13771    )
13772    .await;
13773
13774    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13775    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13776    language_registry.add(rust_lang());
13777    let mut fake_servers = language_registry.register_fake_lsp(
13778        "Rust",
13779        FakeLspAdapter {
13780            capabilities: lsp::ServerCapabilities {
13781                completion_provider: Some(lsp::CompletionOptions {
13782                    resolve_provider: None,
13783                    ..lsp::CompletionOptions::default()
13784                }),
13785                ..lsp::ServerCapabilities::default()
13786            },
13787            ..FakeLspAdapter::default()
13788        },
13789    );
13790    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13791    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13792    let buffer = project
13793        .update(cx, |project, cx| {
13794            project.open_local_buffer(path!("/a/main.rs"), cx)
13795        })
13796        .await
13797        .unwrap();
13798
13799    let multi_buffer = cx.new(|cx| {
13800        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13801        multi_buffer.push_excerpts(
13802            buffer.clone(),
13803            [ExcerptRange::new(0..first_excerpt_end)],
13804            cx,
13805        );
13806        multi_buffer.push_excerpts(
13807            buffer.clone(),
13808            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13809            cx,
13810        );
13811        multi_buffer
13812    });
13813
13814    let editor = workspace
13815        .update(cx, |_, window, cx| {
13816            cx.new(|cx| {
13817                Editor::new(
13818                    EditorMode::Full {
13819                        scale_ui_elements_with_buffer_font_size: false,
13820                        show_active_line_background: false,
13821                        sized_by_content: false,
13822                    },
13823                    multi_buffer.clone(),
13824                    Some(project.clone()),
13825                    window,
13826                    cx,
13827                )
13828            })
13829        })
13830        .unwrap();
13831
13832    let pane = workspace
13833        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13834        .unwrap();
13835    pane.update_in(cx, |pane, window, cx| {
13836        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13837    });
13838
13839    let fake_server = fake_servers.next().await.unwrap();
13840
13841    editor.update_in(cx, |editor, window, cx| {
13842        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13843            s.select_ranges([
13844                Point::new(1, 11)..Point::new(1, 11),
13845                Point::new(7, 11)..Point::new(7, 11),
13846            ])
13847        });
13848
13849        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13850    });
13851
13852    editor.update_in(cx, |editor, window, cx| {
13853        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13854    });
13855
13856    fake_server
13857        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13858            let completion_item = lsp::CompletionItem {
13859                label: "saturating_sub()".into(),
13860                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13861                    lsp::InsertReplaceEdit {
13862                        new_text: "saturating_sub()".to_owned(),
13863                        insert: lsp::Range::new(
13864                            lsp::Position::new(7, 7),
13865                            lsp::Position::new(7, 11),
13866                        ),
13867                        replace: lsp::Range::new(
13868                            lsp::Position::new(7, 7),
13869                            lsp::Position::new(7, 13),
13870                        ),
13871                    },
13872                )),
13873                ..lsp::CompletionItem::default()
13874            };
13875
13876            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13877        })
13878        .next()
13879        .await
13880        .unwrap();
13881
13882    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13883        .await;
13884
13885    editor
13886        .update_in(cx, |editor, window, cx| {
13887            editor
13888                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13889                .unwrap()
13890        })
13891        .await
13892        .unwrap();
13893
13894    editor.update(cx, |editor, cx| {
13895        assert_text_with_selections(editor, expected_multibuffer, cx);
13896    })
13897}
13898
13899#[gpui::test]
13900async fn test_completion(cx: &mut TestAppContext) {
13901    init_test(cx, |_| {});
13902
13903    let mut cx = EditorLspTestContext::new_rust(
13904        lsp::ServerCapabilities {
13905            completion_provider: Some(lsp::CompletionOptions {
13906                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13907                resolve_provider: Some(true),
13908                ..Default::default()
13909            }),
13910            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13911            ..Default::default()
13912        },
13913        cx,
13914    )
13915    .await;
13916    let counter = Arc::new(AtomicUsize::new(0));
13917
13918    cx.set_state(indoc! {"
13919        oneˇ
13920        two
13921        three
13922    "});
13923    cx.simulate_keystroke(".");
13924    handle_completion_request(
13925        indoc! {"
13926            one.|<>
13927            two
13928            three
13929        "},
13930        vec!["first_completion", "second_completion"],
13931        true,
13932        counter.clone(),
13933        &mut cx,
13934    )
13935    .await;
13936    cx.condition(|editor, _| editor.context_menu_visible())
13937        .await;
13938    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13939
13940    let _handler = handle_signature_help_request(
13941        &mut cx,
13942        lsp::SignatureHelp {
13943            signatures: vec![lsp::SignatureInformation {
13944                label: "test signature".to_string(),
13945                documentation: None,
13946                parameters: Some(vec![lsp::ParameterInformation {
13947                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
13948                    documentation: None,
13949                }]),
13950                active_parameter: None,
13951            }],
13952            active_signature: None,
13953            active_parameter: None,
13954        },
13955    );
13956    cx.update_editor(|editor, window, cx| {
13957        assert!(
13958            !editor.signature_help_state.is_shown(),
13959            "No signature help was called for"
13960        );
13961        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13962    });
13963    cx.run_until_parked();
13964    cx.update_editor(|editor, _, _| {
13965        assert!(
13966            !editor.signature_help_state.is_shown(),
13967            "No signature help should be shown when completions menu is open"
13968        );
13969    });
13970
13971    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13972        editor.context_menu_next(&Default::default(), window, cx);
13973        editor
13974            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13975            .unwrap()
13976    });
13977    cx.assert_editor_state(indoc! {"
13978        one.second_completionˇ
13979        two
13980        three
13981    "});
13982
13983    handle_resolve_completion_request(
13984        &mut cx,
13985        Some(vec![
13986            (
13987                //This overlaps with the primary completion edit which is
13988                //misbehavior from the LSP spec, test that we filter it out
13989                indoc! {"
13990                    one.second_ˇcompletion
13991                    two
13992                    threeˇ
13993                "},
13994                "overlapping additional edit",
13995            ),
13996            (
13997                indoc! {"
13998                    one.second_completion
13999                    two
14000                    threeˇ
14001                "},
14002                "\nadditional edit",
14003            ),
14004        ]),
14005    )
14006    .await;
14007    apply_additional_edits.await.unwrap();
14008    cx.assert_editor_state(indoc! {"
14009        one.second_completionˇ
14010        two
14011        three
14012        additional edit
14013    "});
14014
14015    cx.set_state(indoc! {"
14016        one.second_completion
14017        twoˇ
14018        threeˇ
14019        additional edit
14020    "});
14021    cx.simulate_keystroke(" ");
14022    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14023    cx.simulate_keystroke("s");
14024    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14025
14026    cx.assert_editor_state(indoc! {"
14027        one.second_completion
14028        two sˇ
14029        three sˇ
14030        additional edit
14031    "});
14032    handle_completion_request(
14033        indoc! {"
14034            one.second_completion
14035            two s
14036            three <s|>
14037            additional edit
14038        "},
14039        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14040        true,
14041        counter.clone(),
14042        &mut cx,
14043    )
14044    .await;
14045    cx.condition(|editor, _| editor.context_menu_visible())
14046        .await;
14047    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14048
14049    cx.simulate_keystroke("i");
14050
14051    handle_completion_request(
14052        indoc! {"
14053            one.second_completion
14054            two si
14055            three <si|>
14056            additional edit
14057        "},
14058        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14059        true,
14060        counter.clone(),
14061        &mut cx,
14062    )
14063    .await;
14064    cx.condition(|editor, _| editor.context_menu_visible())
14065        .await;
14066    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14067
14068    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14069        editor
14070            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14071            .unwrap()
14072    });
14073    cx.assert_editor_state(indoc! {"
14074        one.second_completion
14075        two sixth_completionˇ
14076        three sixth_completionˇ
14077        additional edit
14078    "});
14079
14080    apply_additional_edits.await.unwrap();
14081
14082    update_test_language_settings(&mut cx, |settings| {
14083        settings.defaults.show_completions_on_input = Some(false);
14084    });
14085    cx.set_state("editorˇ");
14086    cx.simulate_keystroke(".");
14087    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14088    cx.simulate_keystrokes("c l o");
14089    cx.assert_editor_state("editor.cloˇ");
14090    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14091    cx.update_editor(|editor, window, cx| {
14092        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14093    });
14094    handle_completion_request(
14095        "editor.<clo|>",
14096        vec!["close", "clobber"],
14097        true,
14098        counter.clone(),
14099        &mut cx,
14100    )
14101    .await;
14102    cx.condition(|editor, _| editor.context_menu_visible())
14103        .await;
14104    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14105
14106    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14107        editor
14108            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14109            .unwrap()
14110    });
14111    cx.assert_editor_state("editor.clobberˇ");
14112    handle_resolve_completion_request(&mut cx, None).await;
14113    apply_additional_edits.await.unwrap();
14114}
14115
14116#[gpui::test]
14117async fn test_completion_reuse(cx: &mut TestAppContext) {
14118    init_test(cx, |_| {});
14119
14120    let mut cx = EditorLspTestContext::new_rust(
14121        lsp::ServerCapabilities {
14122            completion_provider: Some(lsp::CompletionOptions {
14123                trigger_characters: Some(vec![".".to_string()]),
14124                ..Default::default()
14125            }),
14126            ..Default::default()
14127        },
14128        cx,
14129    )
14130    .await;
14131
14132    let counter = Arc::new(AtomicUsize::new(0));
14133    cx.set_state("objˇ");
14134    cx.simulate_keystroke(".");
14135
14136    // Initial completion request returns complete results
14137    let is_incomplete = false;
14138    handle_completion_request(
14139        "obj.|<>",
14140        vec!["a", "ab", "abc"],
14141        is_incomplete,
14142        counter.clone(),
14143        &mut cx,
14144    )
14145    .await;
14146    cx.run_until_parked();
14147    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14148    cx.assert_editor_state("obj.ˇ");
14149    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14150
14151    // Type "a" - filters existing completions
14152    cx.simulate_keystroke("a");
14153    cx.run_until_parked();
14154    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14155    cx.assert_editor_state("obj.aˇ");
14156    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14157
14158    // Type "b" - filters existing completions
14159    cx.simulate_keystroke("b");
14160    cx.run_until_parked();
14161    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14162    cx.assert_editor_state("obj.abˇ");
14163    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14164
14165    // Type "c" - filters existing completions
14166    cx.simulate_keystroke("c");
14167    cx.run_until_parked();
14168    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14169    cx.assert_editor_state("obj.abcˇ");
14170    check_displayed_completions(vec!["abc"], &mut cx);
14171
14172    // Backspace to delete "c" - filters existing completions
14173    cx.update_editor(|editor, window, cx| {
14174        editor.backspace(&Backspace, window, cx);
14175    });
14176    cx.run_until_parked();
14177    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14178    cx.assert_editor_state("obj.abˇ");
14179    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14180
14181    // Moving cursor to the left dismisses menu.
14182    cx.update_editor(|editor, window, cx| {
14183        editor.move_left(&MoveLeft, window, cx);
14184    });
14185    cx.run_until_parked();
14186    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14187    cx.assert_editor_state("obj.aˇb");
14188    cx.update_editor(|editor, _, _| {
14189        assert_eq!(editor.context_menu_visible(), false);
14190    });
14191
14192    // Type "b" - new request
14193    cx.simulate_keystroke("b");
14194    let is_incomplete = false;
14195    handle_completion_request(
14196        "obj.<ab|>a",
14197        vec!["ab", "abc"],
14198        is_incomplete,
14199        counter.clone(),
14200        &mut cx,
14201    )
14202    .await;
14203    cx.run_until_parked();
14204    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14205    cx.assert_editor_state("obj.abˇb");
14206    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14207
14208    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14209    cx.update_editor(|editor, window, cx| {
14210        editor.backspace(&Backspace, window, cx);
14211    });
14212    let is_incomplete = false;
14213    handle_completion_request(
14214        "obj.<a|>b",
14215        vec!["a", "ab", "abc"],
14216        is_incomplete,
14217        counter.clone(),
14218        &mut cx,
14219    )
14220    .await;
14221    cx.run_until_parked();
14222    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14223    cx.assert_editor_state("obj.aˇb");
14224    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14225
14226    // Backspace to delete "a" - dismisses menu.
14227    cx.update_editor(|editor, window, cx| {
14228        editor.backspace(&Backspace, window, cx);
14229    });
14230    cx.run_until_parked();
14231    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14232    cx.assert_editor_state("obj.ˇb");
14233    cx.update_editor(|editor, _, _| {
14234        assert_eq!(editor.context_menu_visible(), false);
14235    });
14236}
14237
14238#[gpui::test]
14239async fn test_word_completion(cx: &mut TestAppContext) {
14240    let lsp_fetch_timeout_ms = 10;
14241    init_test(cx, |language_settings| {
14242        language_settings.defaults.completions = Some(CompletionSettingsContent {
14243            words_min_length: Some(0),
14244            lsp_fetch_timeout_ms: Some(10),
14245            lsp_insert_mode: Some(LspInsertMode::Insert),
14246            ..Default::default()
14247        });
14248    });
14249
14250    let mut cx = EditorLspTestContext::new_rust(
14251        lsp::ServerCapabilities {
14252            completion_provider: Some(lsp::CompletionOptions {
14253                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14254                ..lsp::CompletionOptions::default()
14255            }),
14256            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14257            ..lsp::ServerCapabilities::default()
14258        },
14259        cx,
14260    )
14261    .await;
14262
14263    let throttle_completions = Arc::new(AtomicBool::new(false));
14264
14265    let lsp_throttle_completions = throttle_completions.clone();
14266    let _completion_requests_handler =
14267        cx.lsp
14268            .server
14269            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14270                let lsp_throttle_completions = lsp_throttle_completions.clone();
14271                let cx = cx.clone();
14272                async move {
14273                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14274                        cx.background_executor()
14275                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14276                            .await;
14277                    }
14278                    Ok(Some(lsp::CompletionResponse::Array(vec![
14279                        lsp::CompletionItem {
14280                            label: "first".into(),
14281                            ..lsp::CompletionItem::default()
14282                        },
14283                        lsp::CompletionItem {
14284                            label: "last".into(),
14285                            ..lsp::CompletionItem::default()
14286                        },
14287                    ])))
14288                }
14289            });
14290
14291    cx.set_state(indoc! {"
14292        oneˇ
14293        two
14294        three
14295    "});
14296    cx.simulate_keystroke(".");
14297    cx.executor().run_until_parked();
14298    cx.condition(|editor, _| editor.context_menu_visible())
14299        .await;
14300    cx.update_editor(|editor, window, cx| {
14301        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14302        {
14303            assert_eq!(
14304                completion_menu_entries(menu),
14305                &["first", "last"],
14306                "When LSP server is fast to reply, no fallback word completions are used"
14307            );
14308        } else {
14309            panic!("expected completion menu to be open");
14310        }
14311        editor.cancel(&Cancel, window, cx);
14312    });
14313    cx.executor().run_until_parked();
14314    cx.condition(|editor, _| !editor.context_menu_visible())
14315        .await;
14316
14317    throttle_completions.store(true, atomic::Ordering::Release);
14318    cx.simulate_keystroke(".");
14319    cx.executor()
14320        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14321    cx.executor().run_until_parked();
14322    cx.condition(|editor, _| editor.context_menu_visible())
14323        .await;
14324    cx.update_editor(|editor, _, _| {
14325        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14326        {
14327            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14328                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14329        } else {
14330            panic!("expected completion menu to be open");
14331        }
14332    });
14333}
14334
14335#[gpui::test]
14336async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14337    init_test(cx, |language_settings| {
14338        language_settings.defaults.completions = Some(CompletionSettingsContent {
14339            words: Some(WordsCompletionMode::Enabled),
14340            words_min_length: Some(0),
14341            lsp_insert_mode: Some(LspInsertMode::Insert),
14342            ..Default::default()
14343        });
14344    });
14345
14346    let mut cx = EditorLspTestContext::new_rust(
14347        lsp::ServerCapabilities {
14348            completion_provider: Some(lsp::CompletionOptions {
14349                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14350                ..lsp::CompletionOptions::default()
14351            }),
14352            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14353            ..lsp::ServerCapabilities::default()
14354        },
14355        cx,
14356    )
14357    .await;
14358
14359    let _completion_requests_handler =
14360        cx.lsp
14361            .server
14362            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14363                Ok(Some(lsp::CompletionResponse::Array(vec![
14364                    lsp::CompletionItem {
14365                        label: "first".into(),
14366                        ..lsp::CompletionItem::default()
14367                    },
14368                    lsp::CompletionItem {
14369                        label: "last".into(),
14370                        ..lsp::CompletionItem::default()
14371                    },
14372                ])))
14373            });
14374
14375    cx.set_state(indoc! {"ˇ
14376        first
14377        last
14378        second
14379    "});
14380    cx.simulate_keystroke(".");
14381    cx.executor().run_until_parked();
14382    cx.condition(|editor, _| editor.context_menu_visible())
14383        .await;
14384    cx.update_editor(|editor, _, _| {
14385        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14386        {
14387            assert_eq!(
14388                completion_menu_entries(menu),
14389                &["first", "last", "second"],
14390                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14391            );
14392        } else {
14393            panic!("expected completion menu to be open");
14394        }
14395    });
14396}
14397
14398#[gpui::test]
14399async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14400    init_test(cx, |language_settings| {
14401        language_settings.defaults.completions = Some(CompletionSettingsContent {
14402            words: Some(WordsCompletionMode::Disabled),
14403            words_min_length: Some(0),
14404            lsp_insert_mode: Some(LspInsertMode::Insert),
14405            ..Default::default()
14406        });
14407    });
14408
14409    let mut cx = EditorLspTestContext::new_rust(
14410        lsp::ServerCapabilities {
14411            completion_provider: Some(lsp::CompletionOptions {
14412                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14413                ..lsp::CompletionOptions::default()
14414            }),
14415            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14416            ..lsp::ServerCapabilities::default()
14417        },
14418        cx,
14419    )
14420    .await;
14421
14422    let _completion_requests_handler =
14423        cx.lsp
14424            .server
14425            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14426                panic!("LSP completions should not be queried when dealing with word completions")
14427            });
14428
14429    cx.set_state(indoc! {"ˇ
14430        first
14431        last
14432        second
14433    "});
14434    cx.update_editor(|editor, window, cx| {
14435        editor.show_word_completions(&ShowWordCompletions, window, cx);
14436    });
14437    cx.executor().run_until_parked();
14438    cx.condition(|editor, _| editor.context_menu_visible())
14439        .await;
14440    cx.update_editor(|editor, _, _| {
14441        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14442        {
14443            assert_eq!(
14444                completion_menu_entries(menu),
14445                &["first", "last", "second"],
14446                "`ShowWordCompletions` action should show word completions"
14447            );
14448        } else {
14449            panic!("expected completion menu to be open");
14450        }
14451    });
14452
14453    cx.simulate_keystroke("l");
14454    cx.executor().run_until_parked();
14455    cx.condition(|editor, _| editor.context_menu_visible())
14456        .await;
14457    cx.update_editor(|editor, _, _| {
14458        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14459        {
14460            assert_eq!(
14461                completion_menu_entries(menu),
14462                &["last"],
14463                "After showing word completions, further editing should filter them and not query the LSP"
14464            );
14465        } else {
14466            panic!("expected completion menu to be open");
14467        }
14468    });
14469}
14470
14471#[gpui::test]
14472async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14473    init_test(cx, |language_settings| {
14474        language_settings.defaults.completions = Some(CompletionSettingsContent {
14475            words_min_length: Some(0),
14476            lsp: Some(false),
14477            lsp_insert_mode: Some(LspInsertMode::Insert),
14478            ..Default::default()
14479        });
14480    });
14481
14482    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14483
14484    cx.set_state(indoc! {"ˇ
14485        0_usize
14486        let
14487        33
14488        4.5f32
14489    "});
14490    cx.update_editor(|editor, window, cx| {
14491        editor.show_completions(&ShowCompletions::default(), window, cx);
14492    });
14493    cx.executor().run_until_parked();
14494    cx.condition(|editor, _| editor.context_menu_visible())
14495        .await;
14496    cx.update_editor(|editor, window, cx| {
14497        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14498        {
14499            assert_eq!(
14500                completion_menu_entries(menu),
14501                &["let"],
14502                "With no digits in the completion query, no digits should be in the word completions"
14503            );
14504        } else {
14505            panic!("expected completion menu to be open");
14506        }
14507        editor.cancel(&Cancel, window, cx);
14508    });
14509
14510    cx.set_state(indoc! {"14511        0_usize
14512        let
14513        3
14514        33.35f32
14515    "});
14516    cx.update_editor(|editor, window, cx| {
14517        editor.show_completions(&ShowCompletions::default(), window, cx);
14518    });
14519    cx.executor().run_until_parked();
14520    cx.condition(|editor, _| editor.context_menu_visible())
14521        .await;
14522    cx.update_editor(|editor, _, _| {
14523        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14524        {
14525            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14526                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14527        } else {
14528            panic!("expected completion menu to be open");
14529        }
14530    });
14531}
14532
14533#[gpui::test]
14534async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14535    init_test(cx, |language_settings| {
14536        language_settings.defaults.completions = Some(CompletionSettingsContent {
14537            words: Some(WordsCompletionMode::Enabled),
14538            words_min_length: Some(3),
14539            lsp_insert_mode: Some(LspInsertMode::Insert),
14540            ..Default::default()
14541        });
14542    });
14543
14544    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14545    cx.set_state(indoc! {"ˇ
14546        wow
14547        wowen
14548        wowser
14549    "});
14550    cx.simulate_keystroke("w");
14551    cx.executor().run_until_parked();
14552    cx.update_editor(|editor, _, _| {
14553        if editor.context_menu.borrow_mut().is_some() {
14554            panic!(
14555                "expected completion menu to be hidden, as words completion threshold is not met"
14556            );
14557        }
14558    });
14559
14560    cx.update_editor(|editor, window, cx| {
14561        editor.show_word_completions(&ShowWordCompletions, window, cx);
14562    });
14563    cx.executor().run_until_parked();
14564    cx.update_editor(|editor, window, cx| {
14565        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14566        {
14567            assert_eq!(completion_menu_entries(menu), &["wowser", "wowen", "wow"], "Even though the threshold is not met, invoking word completions with an action should provide the completions");
14568        } else {
14569            panic!("expected completion menu to be open after the word completions are called with an action");
14570        }
14571
14572        editor.cancel(&Cancel, window, cx);
14573    });
14574    cx.update_editor(|editor, _, _| {
14575        if editor.context_menu.borrow_mut().is_some() {
14576            panic!("expected completion menu to be hidden after canceling");
14577        }
14578    });
14579
14580    cx.simulate_keystroke("o");
14581    cx.executor().run_until_parked();
14582    cx.update_editor(|editor, _, _| {
14583        if editor.context_menu.borrow_mut().is_some() {
14584            panic!(
14585                "expected completion menu to be hidden, as words completion threshold is not met still"
14586            );
14587        }
14588    });
14589
14590    cx.simulate_keystroke("w");
14591    cx.executor().run_until_parked();
14592    cx.update_editor(|editor, _, _| {
14593        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14594        {
14595            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14596        } else {
14597            panic!("expected completion menu to be open after the word completions threshold is met");
14598        }
14599    });
14600}
14601
14602#[gpui::test]
14603async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14604    init_test(cx, |language_settings| {
14605        language_settings.defaults.completions = Some(CompletionSettingsContent {
14606            words: Some(WordsCompletionMode::Enabled),
14607            words_min_length: Some(0),
14608            lsp_insert_mode: Some(LspInsertMode::Insert),
14609            ..Default::default()
14610        });
14611    });
14612
14613    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14614    cx.update_editor(|editor, _, _| {
14615        editor.disable_word_completions();
14616    });
14617    cx.set_state(indoc! {"ˇ
14618        wow
14619        wowen
14620        wowser
14621    "});
14622    cx.simulate_keystroke("w");
14623    cx.executor().run_until_parked();
14624    cx.update_editor(|editor, _, _| {
14625        if editor.context_menu.borrow_mut().is_some() {
14626            panic!(
14627                "expected completion menu to be hidden, as words completion are disabled for this editor"
14628            );
14629        }
14630    });
14631
14632    cx.update_editor(|editor, window, cx| {
14633        editor.show_word_completions(&ShowWordCompletions, window, cx);
14634    });
14635    cx.executor().run_until_parked();
14636    cx.update_editor(|editor, _, _| {
14637        if editor.context_menu.borrow_mut().is_some() {
14638            panic!(
14639                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14640            );
14641        }
14642    });
14643}
14644
14645fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14646    let position = || lsp::Position {
14647        line: params.text_document_position.position.line,
14648        character: params.text_document_position.position.character,
14649    };
14650    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14651        range: lsp::Range {
14652            start: position(),
14653            end: position(),
14654        },
14655        new_text: text.to_string(),
14656    }))
14657}
14658
14659#[gpui::test]
14660async fn test_multiline_completion(cx: &mut TestAppContext) {
14661    init_test(cx, |_| {});
14662
14663    let fs = FakeFs::new(cx.executor());
14664    fs.insert_tree(
14665        path!("/a"),
14666        json!({
14667            "main.ts": "a",
14668        }),
14669    )
14670    .await;
14671
14672    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14673    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14674    let typescript_language = Arc::new(Language::new(
14675        LanguageConfig {
14676            name: "TypeScript".into(),
14677            matcher: LanguageMatcher {
14678                path_suffixes: vec!["ts".to_string()],
14679                ..LanguageMatcher::default()
14680            },
14681            line_comments: vec!["// ".into()],
14682            ..LanguageConfig::default()
14683        },
14684        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14685    ));
14686    language_registry.add(typescript_language.clone());
14687    let mut fake_servers = language_registry.register_fake_lsp(
14688        "TypeScript",
14689        FakeLspAdapter {
14690            capabilities: lsp::ServerCapabilities {
14691                completion_provider: Some(lsp::CompletionOptions {
14692                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14693                    ..lsp::CompletionOptions::default()
14694                }),
14695                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14696                ..lsp::ServerCapabilities::default()
14697            },
14698            // Emulate vtsls label generation
14699            label_for_completion: Some(Box::new(|item, _| {
14700                let text = if let Some(description) = item
14701                    .label_details
14702                    .as_ref()
14703                    .and_then(|label_details| label_details.description.as_ref())
14704                {
14705                    format!("{} {}", item.label, description)
14706                } else if let Some(detail) = &item.detail {
14707                    format!("{} {}", item.label, detail)
14708                } else {
14709                    item.label.clone()
14710                };
14711                let len = text.len();
14712                Some(language::CodeLabel {
14713                    text,
14714                    runs: Vec::new(),
14715                    filter_range: 0..len,
14716                })
14717            })),
14718            ..FakeLspAdapter::default()
14719        },
14720    );
14721    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14722    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14723    let worktree_id = workspace
14724        .update(cx, |workspace, _window, cx| {
14725            workspace.project().update(cx, |project, cx| {
14726                project.worktrees(cx).next().unwrap().read(cx).id()
14727            })
14728        })
14729        .unwrap();
14730    let _buffer = project
14731        .update(cx, |project, cx| {
14732            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14733        })
14734        .await
14735        .unwrap();
14736    let editor = workspace
14737        .update(cx, |workspace, window, cx| {
14738            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
14739        })
14740        .unwrap()
14741        .await
14742        .unwrap()
14743        .downcast::<Editor>()
14744        .unwrap();
14745    let fake_server = fake_servers.next().await.unwrap();
14746
14747    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14748    let multiline_label_2 = "a\nb\nc\n";
14749    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14750    let multiline_description = "d\ne\nf\n";
14751    let multiline_detail_2 = "g\nh\ni\n";
14752
14753    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14754        move |params, _| async move {
14755            Ok(Some(lsp::CompletionResponse::Array(vec![
14756                lsp::CompletionItem {
14757                    label: multiline_label.to_string(),
14758                    text_edit: gen_text_edit(&params, "new_text_1"),
14759                    ..lsp::CompletionItem::default()
14760                },
14761                lsp::CompletionItem {
14762                    label: "single line label 1".to_string(),
14763                    detail: Some(multiline_detail.to_string()),
14764                    text_edit: gen_text_edit(&params, "new_text_2"),
14765                    ..lsp::CompletionItem::default()
14766                },
14767                lsp::CompletionItem {
14768                    label: "single line label 2".to_string(),
14769                    label_details: Some(lsp::CompletionItemLabelDetails {
14770                        description: Some(multiline_description.to_string()),
14771                        detail: None,
14772                    }),
14773                    text_edit: gen_text_edit(&params, "new_text_2"),
14774                    ..lsp::CompletionItem::default()
14775                },
14776                lsp::CompletionItem {
14777                    label: multiline_label_2.to_string(),
14778                    detail: Some(multiline_detail_2.to_string()),
14779                    text_edit: gen_text_edit(&params, "new_text_3"),
14780                    ..lsp::CompletionItem::default()
14781                },
14782                lsp::CompletionItem {
14783                    label: "Label with many     spaces and \t but without newlines".to_string(),
14784                    detail: Some(
14785                        "Details with many     spaces and \t but without newlines".to_string(),
14786                    ),
14787                    text_edit: gen_text_edit(&params, "new_text_4"),
14788                    ..lsp::CompletionItem::default()
14789                },
14790            ])))
14791        },
14792    );
14793
14794    editor.update_in(cx, |editor, window, cx| {
14795        cx.focus_self(window);
14796        editor.move_to_end(&MoveToEnd, window, cx);
14797        editor.handle_input(".", window, cx);
14798    });
14799    cx.run_until_parked();
14800    completion_handle.next().await.unwrap();
14801
14802    editor.update(cx, |editor, _| {
14803        assert!(editor.context_menu_visible());
14804        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14805        {
14806            let completion_labels = menu
14807                .completions
14808                .borrow()
14809                .iter()
14810                .map(|c| c.label.text.clone())
14811                .collect::<Vec<_>>();
14812            assert_eq!(
14813                completion_labels,
14814                &[
14815                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14816                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14817                    "single line label 2 d e f ",
14818                    "a b c g h i ",
14819                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14820                ],
14821                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14822            );
14823
14824            for completion in menu
14825                .completions
14826                .borrow()
14827                .iter() {
14828                    assert_eq!(
14829                        completion.label.filter_range,
14830                        0..completion.label.text.len(),
14831                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14832                    );
14833                }
14834        } else {
14835            panic!("expected completion menu to be open");
14836        }
14837    });
14838}
14839
14840#[gpui::test]
14841async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14842    init_test(cx, |_| {});
14843    let mut cx = EditorLspTestContext::new_rust(
14844        lsp::ServerCapabilities {
14845            completion_provider: Some(lsp::CompletionOptions {
14846                trigger_characters: Some(vec![".".to_string()]),
14847                ..Default::default()
14848            }),
14849            ..Default::default()
14850        },
14851        cx,
14852    )
14853    .await;
14854    cx.lsp
14855        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14856            Ok(Some(lsp::CompletionResponse::Array(vec![
14857                lsp::CompletionItem {
14858                    label: "first".into(),
14859                    ..Default::default()
14860                },
14861                lsp::CompletionItem {
14862                    label: "last".into(),
14863                    ..Default::default()
14864                },
14865            ])))
14866        });
14867    cx.set_state("variableˇ");
14868    cx.simulate_keystroke(".");
14869    cx.executor().run_until_parked();
14870
14871    cx.update_editor(|editor, _, _| {
14872        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14873        {
14874            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14875        } else {
14876            panic!("expected completion menu to be open");
14877        }
14878    });
14879
14880    cx.update_editor(|editor, window, cx| {
14881        editor.move_page_down(&MovePageDown::default(), window, cx);
14882        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14883        {
14884            assert!(
14885                menu.selected_item == 1,
14886                "expected PageDown to select the last item from the context menu"
14887            );
14888        } else {
14889            panic!("expected completion menu to stay open after PageDown");
14890        }
14891    });
14892
14893    cx.update_editor(|editor, window, cx| {
14894        editor.move_page_up(&MovePageUp::default(), window, cx);
14895        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14896        {
14897            assert!(
14898                menu.selected_item == 0,
14899                "expected PageUp to select the first item from the context menu"
14900            );
14901        } else {
14902            panic!("expected completion menu to stay open after PageUp");
14903        }
14904    });
14905}
14906
14907#[gpui::test]
14908async fn test_as_is_completions(cx: &mut TestAppContext) {
14909    init_test(cx, |_| {});
14910    let mut cx = EditorLspTestContext::new_rust(
14911        lsp::ServerCapabilities {
14912            completion_provider: Some(lsp::CompletionOptions {
14913                ..Default::default()
14914            }),
14915            ..Default::default()
14916        },
14917        cx,
14918    )
14919    .await;
14920    cx.lsp
14921        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14922            Ok(Some(lsp::CompletionResponse::Array(vec![
14923                lsp::CompletionItem {
14924                    label: "unsafe".into(),
14925                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14926                        range: lsp::Range {
14927                            start: lsp::Position {
14928                                line: 1,
14929                                character: 2,
14930                            },
14931                            end: lsp::Position {
14932                                line: 1,
14933                                character: 3,
14934                            },
14935                        },
14936                        new_text: "unsafe".to_string(),
14937                    })),
14938                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
14939                    ..Default::default()
14940                },
14941            ])))
14942        });
14943    cx.set_state("fn a() {}\n");
14944    cx.executor().run_until_parked();
14945    cx.update_editor(|editor, window, cx| {
14946        editor.show_completions(
14947            &ShowCompletions {
14948                trigger: Some("\n".into()),
14949            },
14950            window,
14951            cx,
14952        );
14953    });
14954    cx.executor().run_until_parked();
14955
14956    cx.update_editor(|editor, window, cx| {
14957        editor.confirm_completion(&Default::default(), window, cx)
14958    });
14959    cx.executor().run_until_parked();
14960    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
14961}
14962
14963#[gpui::test]
14964async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
14965    init_test(cx, |_| {});
14966    let language =
14967        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
14968    let mut cx = EditorLspTestContext::new(
14969        language,
14970        lsp::ServerCapabilities {
14971            completion_provider: Some(lsp::CompletionOptions {
14972                ..lsp::CompletionOptions::default()
14973            }),
14974            ..lsp::ServerCapabilities::default()
14975        },
14976        cx,
14977    )
14978    .await;
14979
14980    cx.set_state(
14981        "#ifndef BAR_H
14982#define BAR_H
14983
14984#include <stdbool.h>
14985
14986int fn_branch(bool do_branch1, bool do_branch2);
14987
14988#endif // BAR_H
14989ˇ",
14990    );
14991    cx.executor().run_until_parked();
14992    cx.update_editor(|editor, window, cx| {
14993        editor.handle_input("#", window, cx);
14994    });
14995    cx.executor().run_until_parked();
14996    cx.update_editor(|editor, window, cx| {
14997        editor.handle_input("i", window, cx);
14998    });
14999    cx.executor().run_until_parked();
15000    cx.update_editor(|editor, window, cx| {
15001        editor.handle_input("n", window, cx);
15002    });
15003    cx.executor().run_until_parked();
15004    cx.assert_editor_state(
15005        "#ifndef BAR_H
15006#define BAR_H
15007
15008#include <stdbool.h>
15009
15010int fn_branch(bool do_branch1, bool do_branch2);
15011
15012#endif // BAR_H
15013#inˇ",
15014    );
15015
15016    cx.lsp
15017        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15018            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15019                is_incomplete: false,
15020                item_defaults: None,
15021                items: vec![lsp::CompletionItem {
15022                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15023                    label_details: Some(lsp::CompletionItemLabelDetails {
15024                        detail: Some("header".to_string()),
15025                        description: None,
15026                    }),
15027                    label: " include".to_string(),
15028                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15029                        range: lsp::Range {
15030                            start: lsp::Position {
15031                                line: 8,
15032                                character: 1,
15033                            },
15034                            end: lsp::Position {
15035                                line: 8,
15036                                character: 1,
15037                            },
15038                        },
15039                        new_text: "include \"$0\"".to_string(),
15040                    })),
15041                    sort_text: Some("40b67681include".to_string()),
15042                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15043                    filter_text: Some("include".to_string()),
15044                    insert_text: Some("include \"$0\"".to_string()),
15045                    ..lsp::CompletionItem::default()
15046                }],
15047            })))
15048        });
15049    cx.update_editor(|editor, window, cx| {
15050        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15051    });
15052    cx.executor().run_until_parked();
15053    cx.update_editor(|editor, window, cx| {
15054        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15055    });
15056    cx.executor().run_until_parked();
15057    cx.assert_editor_state(
15058        "#ifndef BAR_H
15059#define BAR_H
15060
15061#include <stdbool.h>
15062
15063int fn_branch(bool do_branch1, bool do_branch2);
15064
15065#endif // BAR_H
15066#include \"ˇ\"",
15067    );
15068
15069    cx.lsp
15070        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15071            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15072                is_incomplete: true,
15073                item_defaults: None,
15074                items: vec![lsp::CompletionItem {
15075                    kind: Some(lsp::CompletionItemKind::FILE),
15076                    label: "AGL/".to_string(),
15077                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15078                        range: lsp::Range {
15079                            start: lsp::Position {
15080                                line: 8,
15081                                character: 10,
15082                            },
15083                            end: lsp::Position {
15084                                line: 8,
15085                                character: 11,
15086                            },
15087                        },
15088                        new_text: "AGL/".to_string(),
15089                    })),
15090                    sort_text: Some("40b67681AGL/".to_string()),
15091                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15092                    filter_text: Some("AGL/".to_string()),
15093                    insert_text: Some("AGL/".to_string()),
15094                    ..lsp::CompletionItem::default()
15095                }],
15096            })))
15097        });
15098    cx.update_editor(|editor, window, cx| {
15099        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15100    });
15101    cx.executor().run_until_parked();
15102    cx.update_editor(|editor, window, cx| {
15103        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15104    });
15105    cx.executor().run_until_parked();
15106    cx.assert_editor_state(
15107        r##"#ifndef BAR_H
15108#define BAR_H
15109
15110#include <stdbool.h>
15111
15112int fn_branch(bool do_branch1, bool do_branch2);
15113
15114#endif // BAR_H
15115#include "AGL/ˇ"##,
15116    );
15117
15118    cx.update_editor(|editor, window, cx| {
15119        editor.handle_input("\"", window, cx);
15120    });
15121    cx.executor().run_until_parked();
15122    cx.assert_editor_state(
15123        r##"#ifndef BAR_H
15124#define BAR_H
15125
15126#include <stdbool.h>
15127
15128int fn_branch(bool do_branch1, bool do_branch2);
15129
15130#endif // BAR_H
15131#include "AGL/"ˇ"##,
15132    );
15133}
15134
15135#[gpui::test]
15136async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15137    init_test(cx, |_| {});
15138
15139    let mut cx = EditorLspTestContext::new_rust(
15140        lsp::ServerCapabilities {
15141            completion_provider: Some(lsp::CompletionOptions {
15142                trigger_characters: Some(vec![".".to_string()]),
15143                resolve_provider: Some(true),
15144                ..Default::default()
15145            }),
15146            ..Default::default()
15147        },
15148        cx,
15149    )
15150    .await;
15151
15152    cx.set_state("fn main() { let a = 2ˇ; }");
15153    cx.simulate_keystroke(".");
15154    let completion_item = lsp::CompletionItem {
15155        label: "Some".into(),
15156        kind: Some(lsp::CompletionItemKind::SNIPPET),
15157        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15158        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15159            kind: lsp::MarkupKind::Markdown,
15160            value: "```rust\nSome(2)\n```".to_string(),
15161        })),
15162        deprecated: Some(false),
15163        sort_text: Some("Some".to_string()),
15164        filter_text: Some("Some".to_string()),
15165        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15166        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15167            range: lsp::Range {
15168                start: lsp::Position {
15169                    line: 0,
15170                    character: 22,
15171                },
15172                end: lsp::Position {
15173                    line: 0,
15174                    character: 22,
15175                },
15176            },
15177            new_text: "Some(2)".to_string(),
15178        })),
15179        additional_text_edits: Some(vec![lsp::TextEdit {
15180            range: lsp::Range {
15181                start: lsp::Position {
15182                    line: 0,
15183                    character: 20,
15184                },
15185                end: lsp::Position {
15186                    line: 0,
15187                    character: 22,
15188                },
15189            },
15190            new_text: "".to_string(),
15191        }]),
15192        ..Default::default()
15193    };
15194
15195    let closure_completion_item = completion_item.clone();
15196    let counter = Arc::new(AtomicUsize::new(0));
15197    let counter_clone = counter.clone();
15198    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15199        let task_completion_item = closure_completion_item.clone();
15200        counter_clone.fetch_add(1, atomic::Ordering::Release);
15201        async move {
15202            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15203                is_incomplete: true,
15204                item_defaults: None,
15205                items: vec![task_completion_item],
15206            })))
15207        }
15208    });
15209
15210    cx.condition(|editor, _| editor.context_menu_visible())
15211        .await;
15212    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15213    assert!(request.next().await.is_some());
15214    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15215
15216    cx.simulate_keystrokes("S o m");
15217    cx.condition(|editor, _| editor.context_menu_visible())
15218        .await;
15219    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15220    assert!(request.next().await.is_some());
15221    assert!(request.next().await.is_some());
15222    assert!(request.next().await.is_some());
15223    request.close();
15224    assert!(request.next().await.is_none());
15225    assert_eq!(
15226        counter.load(atomic::Ordering::Acquire),
15227        4,
15228        "With the completions menu open, only one LSP request should happen per input"
15229    );
15230}
15231
15232#[gpui::test]
15233async fn test_toggle_comment(cx: &mut TestAppContext) {
15234    init_test(cx, |_| {});
15235    let mut cx = EditorTestContext::new(cx).await;
15236    let language = Arc::new(Language::new(
15237        LanguageConfig {
15238            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15239            ..Default::default()
15240        },
15241        Some(tree_sitter_rust::LANGUAGE.into()),
15242    ));
15243    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15244
15245    // If multiple selections intersect a line, the line is only toggled once.
15246    cx.set_state(indoc! {"
15247        fn a() {
15248            «//b();
15249            ˇ»// «c();
15250            //ˇ»  d();
15251        }
15252    "});
15253
15254    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15255
15256    cx.assert_editor_state(indoc! {"
15257        fn a() {
15258            «b();
15259            c();
15260            ˇ» d();
15261        }
15262    "});
15263
15264    // The comment prefix is inserted at the same column for every line in a
15265    // selection.
15266    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15267
15268    cx.assert_editor_state(indoc! {"
15269        fn a() {
15270            // «b();
15271            // c();
15272            ˇ»//  d();
15273        }
15274    "});
15275
15276    // If a selection ends at the beginning of a line, that line is not toggled.
15277    cx.set_selections_state(indoc! {"
15278        fn a() {
15279            // b();
15280            «// c();
15281        ˇ»    //  d();
15282        }
15283    "});
15284
15285    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15286
15287    cx.assert_editor_state(indoc! {"
15288        fn a() {
15289            // b();
15290            «c();
15291        ˇ»    //  d();
15292        }
15293    "});
15294
15295    // If a selection span a single line and is empty, the line is toggled.
15296    cx.set_state(indoc! {"
15297        fn a() {
15298            a();
15299            b();
15300        ˇ
15301        }
15302    "});
15303
15304    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15305
15306    cx.assert_editor_state(indoc! {"
15307        fn a() {
15308            a();
15309            b();
15310        //•ˇ
15311        }
15312    "});
15313
15314    // If a selection span multiple lines, empty lines are not toggled.
15315    cx.set_state(indoc! {"
15316        fn a() {
15317            «a();
15318
15319            c();ˇ»
15320        }
15321    "});
15322
15323    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15324
15325    cx.assert_editor_state(indoc! {"
15326        fn a() {
15327            // «a();
15328
15329            // c();ˇ»
15330        }
15331    "});
15332
15333    // If a selection includes multiple comment prefixes, all lines are uncommented.
15334    cx.set_state(indoc! {"
15335        fn a() {
15336            «// a();
15337            /// b();
15338            //! c();ˇ»
15339        }
15340    "});
15341
15342    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15343
15344    cx.assert_editor_state(indoc! {"
15345        fn a() {
15346            «a();
15347            b();
15348            c();ˇ»
15349        }
15350    "});
15351}
15352
15353#[gpui::test]
15354async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15355    init_test(cx, |_| {});
15356    let mut cx = EditorTestContext::new(cx).await;
15357    let language = Arc::new(Language::new(
15358        LanguageConfig {
15359            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15360            ..Default::default()
15361        },
15362        Some(tree_sitter_rust::LANGUAGE.into()),
15363    ));
15364    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15365
15366    let toggle_comments = &ToggleComments {
15367        advance_downwards: false,
15368        ignore_indent: true,
15369    };
15370
15371    // If multiple selections intersect a line, the line is only toggled once.
15372    cx.set_state(indoc! {"
15373        fn a() {
15374        //    «b();
15375        //    c();
15376        //    ˇ» d();
15377        }
15378    "});
15379
15380    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15381
15382    cx.assert_editor_state(indoc! {"
15383        fn a() {
15384            «b();
15385            c();
15386            ˇ» d();
15387        }
15388    "});
15389
15390    // The comment prefix is inserted at the beginning of each line
15391    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15392
15393    cx.assert_editor_state(indoc! {"
15394        fn a() {
15395        //    «b();
15396        //    c();
15397        //    ˇ» d();
15398        }
15399    "});
15400
15401    // If a selection ends at the beginning of a line, that line is not toggled.
15402    cx.set_selections_state(indoc! {"
15403        fn a() {
15404        //    b();
15405        //    «c();
15406        ˇ»//     d();
15407        }
15408    "});
15409
15410    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15411
15412    cx.assert_editor_state(indoc! {"
15413        fn a() {
15414        //    b();
15415            «c();
15416        ˇ»//     d();
15417        }
15418    "});
15419
15420    // If a selection span a single line and is empty, the line is toggled.
15421    cx.set_state(indoc! {"
15422        fn a() {
15423            a();
15424            b();
15425        ˇ
15426        }
15427    "});
15428
15429    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15430
15431    cx.assert_editor_state(indoc! {"
15432        fn a() {
15433            a();
15434            b();
15435        //ˇ
15436        }
15437    "});
15438
15439    // If a selection span multiple lines, empty lines are not toggled.
15440    cx.set_state(indoc! {"
15441        fn a() {
15442            «a();
15443
15444            c();ˇ»
15445        }
15446    "});
15447
15448    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15449
15450    cx.assert_editor_state(indoc! {"
15451        fn a() {
15452        //    «a();
15453
15454        //    c();ˇ»
15455        }
15456    "});
15457
15458    // If a selection includes multiple comment prefixes, all lines are uncommented.
15459    cx.set_state(indoc! {"
15460        fn a() {
15461        //    «a();
15462        ///    b();
15463        //!    c();ˇ»
15464        }
15465    "});
15466
15467    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15468
15469    cx.assert_editor_state(indoc! {"
15470        fn a() {
15471            «a();
15472            b();
15473            c();ˇ»
15474        }
15475    "});
15476}
15477
15478#[gpui::test]
15479async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15480    init_test(cx, |_| {});
15481
15482    let language = Arc::new(Language::new(
15483        LanguageConfig {
15484            line_comments: vec!["// ".into()],
15485            ..Default::default()
15486        },
15487        Some(tree_sitter_rust::LANGUAGE.into()),
15488    ));
15489
15490    let mut cx = EditorTestContext::new(cx).await;
15491
15492    cx.language_registry().add(language.clone());
15493    cx.update_buffer(|buffer, cx| {
15494        buffer.set_language(Some(language), cx);
15495    });
15496
15497    let toggle_comments = &ToggleComments {
15498        advance_downwards: true,
15499        ignore_indent: false,
15500    };
15501
15502    // Single cursor on one line -> advance
15503    // Cursor moves horizontally 3 characters as well on non-blank line
15504    cx.set_state(indoc!(
15505        "fn a() {
15506             ˇdog();
15507             cat();
15508        }"
15509    ));
15510    cx.update_editor(|editor, window, cx| {
15511        editor.toggle_comments(toggle_comments, window, cx);
15512    });
15513    cx.assert_editor_state(indoc!(
15514        "fn a() {
15515             // dog();
15516             catˇ();
15517        }"
15518    ));
15519
15520    // Single selection on one line -> don't advance
15521    cx.set_state(indoc!(
15522        "fn a() {
15523             «dog()ˇ»;
15524             cat();
15525        }"
15526    ));
15527    cx.update_editor(|editor, window, cx| {
15528        editor.toggle_comments(toggle_comments, window, cx);
15529    });
15530    cx.assert_editor_state(indoc!(
15531        "fn a() {
15532             // «dog()ˇ»;
15533             cat();
15534        }"
15535    ));
15536
15537    // Multiple cursors on one line -> advance
15538    cx.set_state(indoc!(
15539        "fn a() {
15540             ˇdˇog();
15541             cat();
15542        }"
15543    ));
15544    cx.update_editor(|editor, window, cx| {
15545        editor.toggle_comments(toggle_comments, window, cx);
15546    });
15547    cx.assert_editor_state(indoc!(
15548        "fn a() {
15549             // dog();
15550             catˇ(ˇ);
15551        }"
15552    ));
15553
15554    // Multiple cursors on one line, with selection -> don't advance
15555    cx.set_state(indoc!(
15556        "fn a() {
15557             ˇdˇog«()ˇ»;
15558             cat();
15559        }"
15560    ));
15561    cx.update_editor(|editor, window, cx| {
15562        editor.toggle_comments(toggle_comments, window, cx);
15563    });
15564    cx.assert_editor_state(indoc!(
15565        "fn a() {
15566             // ˇdˇog«()ˇ»;
15567             cat();
15568        }"
15569    ));
15570
15571    // Single cursor on one line -> advance
15572    // Cursor moves to column 0 on blank line
15573    cx.set_state(indoc!(
15574        "fn a() {
15575             ˇdog();
15576
15577             cat();
15578        }"
15579    ));
15580    cx.update_editor(|editor, window, cx| {
15581        editor.toggle_comments(toggle_comments, window, cx);
15582    });
15583    cx.assert_editor_state(indoc!(
15584        "fn a() {
15585             // dog();
15586        ˇ
15587             cat();
15588        }"
15589    ));
15590
15591    // Single cursor on one line -> advance
15592    // Cursor starts and ends at column 0
15593    cx.set_state(indoc!(
15594        "fn a() {
15595         ˇ    dog();
15596             cat();
15597        }"
15598    ));
15599    cx.update_editor(|editor, window, cx| {
15600        editor.toggle_comments(toggle_comments, window, cx);
15601    });
15602    cx.assert_editor_state(indoc!(
15603        "fn a() {
15604             // dog();
15605         ˇ    cat();
15606        }"
15607    ));
15608}
15609
15610#[gpui::test]
15611async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15612    init_test(cx, |_| {});
15613
15614    let mut cx = EditorTestContext::new(cx).await;
15615
15616    let html_language = Arc::new(
15617        Language::new(
15618            LanguageConfig {
15619                name: "HTML".into(),
15620                block_comment: Some(BlockCommentConfig {
15621                    start: "<!-- ".into(),
15622                    prefix: "".into(),
15623                    end: " -->".into(),
15624                    tab_size: 0,
15625                }),
15626                ..Default::default()
15627            },
15628            Some(tree_sitter_html::LANGUAGE.into()),
15629        )
15630        .with_injection_query(
15631            r#"
15632            (script_element
15633                (raw_text) @injection.content
15634                (#set! injection.language "javascript"))
15635            "#,
15636        )
15637        .unwrap(),
15638    );
15639
15640    let javascript_language = Arc::new(Language::new(
15641        LanguageConfig {
15642            name: "JavaScript".into(),
15643            line_comments: vec!["// ".into()],
15644            ..Default::default()
15645        },
15646        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15647    ));
15648
15649    cx.language_registry().add(html_language.clone());
15650    cx.language_registry().add(javascript_language);
15651    cx.update_buffer(|buffer, cx| {
15652        buffer.set_language(Some(html_language), cx);
15653    });
15654
15655    // Toggle comments for empty selections
15656    cx.set_state(
15657        &r#"
15658            <p>A</p>ˇ
15659            <p>B</p>ˇ
15660            <p>C</p>ˇ
15661        "#
15662        .unindent(),
15663    );
15664    cx.update_editor(|editor, window, cx| {
15665        editor.toggle_comments(&ToggleComments::default(), window, cx)
15666    });
15667    cx.assert_editor_state(
15668        &r#"
15669            <!-- <p>A</p>ˇ -->
15670            <!-- <p>B</p>ˇ -->
15671            <!-- <p>C</p>ˇ -->
15672        "#
15673        .unindent(),
15674    );
15675    cx.update_editor(|editor, window, cx| {
15676        editor.toggle_comments(&ToggleComments::default(), window, cx)
15677    });
15678    cx.assert_editor_state(
15679        &r#"
15680            <p>A</p>ˇ
15681            <p>B</p>ˇ
15682            <p>C</p>ˇ
15683        "#
15684        .unindent(),
15685    );
15686
15687    // Toggle comments for mixture of empty and non-empty selections, where
15688    // multiple selections occupy a given line.
15689    cx.set_state(
15690        &r#"
15691            <p>A«</p>
15692            <p>ˇ»B</p>ˇ
15693            <p>C«</p>
15694            <p>ˇ»D</p>ˇ
15695        "#
15696        .unindent(),
15697    );
15698
15699    cx.update_editor(|editor, window, cx| {
15700        editor.toggle_comments(&ToggleComments::default(), window, cx)
15701    });
15702    cx.assert_editor_state(
15703        &r#"
15704            <!-- <p>A«</p>
15705            <p>ˇ»B</p>ˇ -->
15706            <!-- <p>C«</p>
15707            <p>ˇ»D</p>ˇ -->
15708        "#
15709        .unindent(),
15710    );
15711    cx.update_editor(|editor, window, cx| {
15712        editor.toggle_comments(&ToggleComments::default(), window, cx)
15713    });
15714    cx.assert_editor_state(
15715        &r#"
15716            <p>A«</p>
15717            <p>ˇ»B</p>ˇ
15718            <p>C«</p>
15719            <p>ˇ»D</p>ˇ
15720        "#
15721        .unindent(),
15722    );
15723
15724    // Toggle comments when different languages are active for different
15725    // selections.
15726    cx.set_state(
15727        &r#"
15728            ˇ<script>
15729                ˇvar x = new Y();
15730            ˇ</script>
15731        "#
15732        .unindent(),
15733    );
15734    cx.executor().run_until_parked();
15735    cx.update_editor(|editor, window, cx| {
15736        editor.toggle_comments(&ToggleComments::default(), window, cx)
15737    });
15738    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15739    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15740    cx.assert_editor_state(
15741        &r#"
15742            <!-- ˇ<script> -->
15743                // ˇvar x = new Y();
15744            <!-- ˇ</script> -->
15745        "#
15746        .unindent(),
15747    );
15748}
15749
15750#[gpui::test]
15751fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15752    init_test(cx, |_| {});
15753
15754    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15755    let multibuffer = cx.new(|cx| {
15756        let mut multibuffer = MultiBuffer::new(ReadWrite);
15757        multibuffer.push_excerpts(
15758            buffer.clone(),
15759            [
15760                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15761                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15762            ],
15763            cx,
15764        );
15765        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15766        multibuffer
15767    });
15768
15769    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15770    editor.update_in(cx, |editor, window, cx| {
15771        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15772        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15773            s.select_ranges([
15774                Point::new(0, 0)..Point::new(0, 0),
15775                Point::new(1, 0)..Point::new(1, 0),
15776            ])
15777        });
15778
15779        editor.handle_input("X", window, cx);
15780        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15781        assert_eq!(
15782            editor.selections.ranges(cx),
15783            [
15784                Point::new(0, 1)..Point::new(0, 1),
15785                Point::new(1, 1)..Point::new(1, 1),
15786            ]
15787        );
15788
15789        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15790        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15791            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15792        });
15793        editor.backspace(&Default::default(), window, cx);
15794        assert_eq!(editor.text(cx), "Xa\nbbb");
15795        assert_eq!(
15796            editor.selections.ranges(cx),
15797            [Point::new(1, 0)..Point::new(1, 0)]
15798        );
15799
15800        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15801            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15802        });
15803        editor.backspace(&Default::default(), window, cx);
15804        assert_eq!(editor.text(cx), "X\nbb");
15805        assert_eq!(
15806            editor.selections.ranges(cx),
15807            [Point::new(0, 1)..Point::new(0, 1)]
15808        );
15809    });
15810}
15811
15812#[gpui::test]
15813fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15814    init_test(cx, |_| {});
15815
15816    let markers = vec![('[', ']').into(), ('(', ')').into()];
15817    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15818        indoc! {"
15819            [aaaa
15820            (bbbb]
15821            cccc)",
15822        },
15823        markers.clone(),
15824    );
15825    let excerpt_ranges = markers.into_iter().map(|marker| {
15826        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15827        ExcerptRange::new(context)
15828    });
15829    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15830    let multibuffer = cx.new(|cx| {
15831        let mut multibuffer = MultiBuffer::new(ReadWrite);
15832        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15833        multibuffer
15834    });
15835
15836    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15837    editor.update_in(cx, |editor, window, cx| {
15838        let (expected_text, selection_ranges) = marked_text_ranges(
15839            indoc! {"
15840                aaaa
15841                bˇbbb
15842                bˇbbˇb
15843                cccc"
15844            },
15845            true,
15846        );
15847        assert_eq!(editor.text(cx), expected_text);
15848        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15849            s.select_ranges(selection_ranges)
15850        });
15851
15852        editor.handle_input("X", window, cx);
15853
15854        let (expected_text, expected_selections) = marked_text_ranges(
15855            indoc! {"
15856                aaaa
15857                bXˇbbXb
15858                bXˇbbXˇb
15859                cccc"
15860            },
15861            false,
15862        );
15863        assert_eq!(editor.text(cx), expected_text);
15864        assert_eq!(editor.selections.ranges(cx), expected_selections);
15865
15866        editor.newline(&Newline, window, cx);
15867        let (expected_text, expected_selections) = marked_text_ranges(
15868            indoc! {"
15869                aaaa
15870                bX
15871                ˇbbX
15872                b
15873                bX
15874                ˇbbX
15875                ˇb
15876                cccc"
15877            },
15878            false,
15879        );
15880        assert_eq!(editor.text(cx), expected_text);
15881        assert_eq!(editor.selections.ranges(cx), expected_selections);
15882    });
15883}
15884
15885#[gpui::test]
15886fn test_refresh_selections(cx: &mut TestAppContext) {
15887    init_test(cx, |_| {});
15888
15889    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15890    let mut excerpt1_id = None;
15891    let multibuffer = cx.new(|cx| {
15892        let mut multibuffer = MultiBuffer::new(ReadWrite);
15893        excerpt1_id = multibuffer
15894            .push_excerpts(
15895                buffer.clone(),
15896                [
15897                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15898                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15899                ],
15900                cx,
15901            )
15902            .into_iter()
15903            .next();
15904        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15905        multibuffer
15906    });
15907
15908    let editor = cx.add_window(|window, cx| {
15909        let mut editor = build_editor(multibuffer.clone(), window, cx);
15910        let snapshot = editor.snapshot(window, cx);
15911        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15912            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15913        });
15914        editor.begin_selection(
15915            Point::new(2, 1).to_display_point(&snapshot),
15916            true,
15917            1,
15918            window,
15919            cx,
15920        );
15921        assert_eq!(
15922            editor.selections.ranges(cx),
15923            [
15924                Point::new(1, 3)..Point::new(1, 3),
15925                Point::new(2, 1)..Point::new(2, 1),
15926            ]
15927        );
15928        editor
15929    });
15930
15931    // Refreshing selections is a no-op when excerpts haven't changed.
15932    _ = editor.update(cx, |editor, window, cx| {
15933        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15934        assert_eq!(
15935            editor.selections.ranges(cx),
15936            [
15937                Point::new(1, 3)..Point::new(1, 3),
15938                Point::new(2, 1)..Point::new(2, 1),
15939            ]
15940        );
15941    });
15942
15943    multibuffer.update(cx, |multibuffer, cx| {
15944        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15945    });
15946    _ = editor.update(cx, |editor, window, cx| {
15947        // Removing an excerpt causes the first selection to become degenerate.
15948        assert_eq!(
15949            editor.selections.ranges(cx),
15950            [
15951                Point::new(0, 0)..Point::new(0, 0),
15952                Point::new(0, 1)..Point::new(0, 1)
15953            ]
15954        );
15955
15956        // Refreshing selections will relocate the first selection to the original buffer
15957        // location.
15958        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15959        assert_eq!(
15960            editor.selections.ranges(cx),
15961            [
15962                Point::new(0, 1)..Point::new(0, 1),
15963                Point::new(0, 3)..Point::new(0, 3)
15964            ]
15965        );
15966        assert!(editor.selections.pending_anchor().is_some());
15967    });
15968}
15969
15970#[gpui::test]
15971fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
15972    init_test(cx, |_| {});
15973
15974    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15975    let mut excerpt1_id = None;
15976    let multibuffer = cx.new(|cx| {
15977        let mut multibuffer = MultiBuffer::new(ReadWrite);
15978        excerpt1_id = multibuffer
15979            .push_excerpts(
15980                buffer.clone(),
15981                [
15982                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15983                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15984                ],
15985                cx,
15986            )
15987            .into_iter()
15988            .next();
15989        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15990        multibuffer
15991    });
15992
15993    let editor = cx.add_window(|window, cx| {
15994        let mut editor = build_editor(multibuffer.clone(), window, cx);
15995        let snapshot = editor.snapshot(window, cx);
15996        editor.begin_selection(
15997            Point::new(1, 3).to_display_point(&snapshot),
15998            false,
15999            1,
16000            window,
16001            cx,
16002        );
16003        assert_eq!(
16004            editor.selections.ranges(cx),
16005            [Point::new(1, 3)..Point::new(1, 3)]
16006        );
16007        editor
16008    });
16009
16010    multibuffer.update(cx, |multibuffer, cx| {
16011        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16012    });
16013    _ = editor.update(cx, |editor, window, cx| {
16014        assert_eq!(
16015            editor.selections.ranges(cx),
16016            [Point::new(0, 0)..Point::new(0, 0)]
16017        );
16018
16019        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16020        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16021        assert_eq!(
16022            editor.selections.ranges(cx),
16023            [Point::new(0, 3)..Point::new(0, 3)]
16024        );
16025        assert!(editor.selections.pending_anchor().is_some());
16026    });
16027}
16028
16029#[gpui::test]
16030async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16031    init_test(cx, |_| {});
16032
16033    let language = Arc::new(
16034        Language::new(
16035            LanguageConfig {
16036                brackets: BracketPairConfig {
16037                    pairs: vec![
16038                        BracketPair {
16039                            start: "{".to_string(),
16040                            end: "}".to_string(),
16041                            close: true,
16042                            surround: true,
16043                            newline: true,
16044                        },
16045                        BracketPair {
16046                            start: "/* ".to_string(),
16047                            end: " */".to_string(),
16048                            close: true,
16049                            surround: true,
16050                            newline: true,
16051                        },
16052                    ],
16053                    ..Default::default()
16054                },
16055                ..Default::default()
16056            },
16057            Some(tree_sitter_rust::LANGUAGE.into()),
16058        )
16059        .with_indents_query("")
16060        .unwrap(),
16061    );
16062
16063    let text = concat!(
16064        "{   }\n",     //
16065        "  x\n",       //
16066        "  /*   */\n", //
16067        "x\n",         //
16068        "{{} }\n",     //
16069    );
16070
16071    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16072    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16073    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16074    editor
16075        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16076        .await;
16077
16078    editor.update_in(cx, |editor, window, cx| {
16079        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16080            s.select_display_ranges([
16081                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16082                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16083                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16084            ])
16085        });
16086        editor.newline(&Newline, window, cx);
16087
16088        assert_eq!(
16089            editor.buffer().read(cx).read(cx).text(),
16090            concat!(
16091                "{ \n",    // Suppress rustfmt
16092                "\n",      //
16093                "}\n",     //
16094                "  x\n",   //
16095                "  /* \n", //
16096                "  \n",    //
16097                "  */\n",  //
16098                "x\n",     //
16099                "{{} \n",  //
16100                "}\n",     //
16101            )
16102        );
16103    });
16104}
16105
16106#[gpui::test]
16107fn test_highlighted_ranges(cx: &mut TestAppContext) {
16108    init_test(cx, |_| {});
16109
16110    let editor = cx.add_window(|window, cx| {
16111        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16112        build_editor(buffer, window, cx)
16113    });
16114
16115    _ = editor.update(cx, |editor, window, cx| {
16116        struct Type1;
16117        struct Type2;
16118
16119        let buffer = editor.buffer.read(cx).snapshot(cx);
16120
16121        let anchor_range =
16122            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16123
16124        editor.highlight_background::<Type1>(
16125            &[
16126                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16127                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16128                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16129                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16130            ],
16131            |_| Hsla::red(),
16132            cx,
16133        );
16134        editor.highlight_background::<Type2>(
16135            &[
16136                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16137                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16138                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16139                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16140            ],
16141            |_| Hsla::green(),
16142            cx,
16143        );
16144
16145        let snapshot = editor.snapshot(window, cx);
16146        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16147            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16148            &snapshot,
16149            cx.theme(),
16150        );
16151        assert_eq!(
16152            highlighted_ranges,
16153            &[
16154                (
16155                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16156                    Hsla::green(),
16157                ),
16158                (
16159                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16160                    Hsla::red(),
16161                ),
16162                (
16163                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16164                    Hsla::green(),
16165                ),
16166                (
16167                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16168                    Hsla::red(),
16169                ),
16170            ]
16171        );
16172        assert_eq!(
16173            editor.sorted_background_highlights_in_range(
16174                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16175                &snapshot,
16176                cx.theme(),
16177            ),
16178            &[(
16179                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16180                Hsla::red(),
16181            )]
16182        );
16183    });
16184}
16185
16186#[gpui::test]
16187async fn test_following(cx: &mut TestAppContext) {
16188    init_test(cx, |_| {});
16189
16190    let fs = FakeFs::new(cx.executor());
16191    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16192
16193    let buffer = project.update(cx, |project, cx| {
16194        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16195        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16196    });
16197    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16198    let follower = cx.update(|cx| {
16199        cx.open_window(
16200            WindowOptions {
16201                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16202                    gpui::Point::new(px(0.), px(0.)),
16203                    gpui::Point::new(px(10.), px(80.)),
16204                ))),
16205                ..Default::default()
16206            },
16207            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16208        )
16209        .unwrap()
16210    });
16211
16212    let is_still_following = Rc::new(RefCell::new(true));
16213    let follower_edit_event_count = Rc::new(RefCell::new(0));
16214    let pending_update = Rc::new(RefCell::new(None));
16215    let leader_entity = leader.root(cx).unwrap();
16216    let follower_entity = follower.root(cx).unwrap();
16217    _ = follower.update(cx, {
16218        let update = pending_update.clone();
16219        let is_still_following = is_still_following.clone();
16220        let follower_edit_event_count = follower_edit_event_count.clone();
16221        |_, window, cx| {
16222            cx.subscribe_in(
16223                &leader_entity,
16224                window,
16225                move |_, leader, event, window, cx| {
16226                    leader.read(cx).add_event_to_update_proto(
16227                        event,
16228                        &mut update.borrow_mut(),
16229                        window,
16230                        cx,
16231                    );
16232                },
16233            )
16234            .detach();
16235
16236            cx.subscribe_in(
16237                &follower_entity,
16238                window,
16239                move |_, _, event: &EditorEvent, _window, _cx| {
16240                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16241                        *is_still_following.borrow_mut() = false;
16242                    }
16243
16244                    if let EditorEvent::BufferEdited = event {
16245                        *follower_edit_event_count.borrow_mut() += 1;
16246                    }
16247                },
16248            )
16249            .detach();
16250        }
16251    });
16252
16253    // Update the selections only
16254    _ = leader.update(cx, |leader, window, cx| {
16255        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16256            s.select_ranges([1..1])
16257        });
16258    });
16259    follower
16260        .update(cx, |follower, window, cx| {
16261            follower.apply_update_proto(
16262                &project,
16263                pending_update.borrow_mut().take().unwrap(),
16264                window,
16265                cx,
16266            )
16267        })
16268        .unwrap()
16269        .await
16270        .unwrap();
16271    _ = follower.update(cx, |follower, _, cx| {
16272        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
16273    });
16274    assert!(*is_still_following.borrow());
16275    assert_eq!(*follower_edit_event_count.borrow(), 0);
16276
16277    // Update the scroll position only
16278    _ = leader.update(cx, |leader, window, cx| {
16279        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16280    });
16281    follower
16282        .update(cx, |follower, window, cx| {
16283            follower.apply_update_proto(
16284                &project,
16285                pending_update.borrow_mut().take().unwrap(),
16286                window,
16287                cx,
16288            )
16289        })
16290        .unwrap()
16291        .await
16292        .unwrap();
16293    assert_eq!(
16294        follower
16295            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16296            .unwrap(),
16297        gpui::Point::new(1.5, 3.5)
16298    );
16299    assert!(*is_still_following.borrow());
16300    assert_eq!(*follower_edit_event_count.borrow(), 0);
16301
16302    // Update the selections and scroll position. The follower's scroll position is updated
16303    // via autoscroll, not via the leader's exact scroll position.
16304    _ = leader.update(cx, |leader, window, cx| {
16305        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16306            s.select_ranges([0..0])
16307        });
16308        leader.request_autoscroll(Autoscroll::newest(), cx);
16309        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16310    });
16311    follower
16312        .update(cx, |follower, window, cx| {
16313            follower.apply_update_proto(
16314                &project,
16315                pending_update.borrow_mut().take().unwrap(),
16316                window,
16317                cx,
16318            )
16319        })
16320        .unwrap()
16321        .await
16322        .unwrap();
16323    _ = follower.update(cx, |follower, _, cx| {
16324        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16325        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16326    });
16327    assert!(*is_still_following.borrow());
16328
16329    // Creating a pending selection that precedes another selection
16330    _ = leader.update(cx, |leader, window, cx| {
16331        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16332            s.select_ranges([1..1])
16333        });
16334        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16335    });
16336    follower
16337        .update(cx, |follower, window, cx| {
16338            follower.apply_update_proto(
16339                &project,
16340                pending_update.borrow_mut().take().unwrap(),
16341                window,
16342                cx,
16343            )
16344        })
16345        .unwrap()
16346        .await
16347        .unwrap();
16348    _ = follower.update(cx, |follower, _, cx| {
16349        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16350    });
16351    assert!(*is_still_following.borrow());
16352
16353    // Extend the pending selection so that it surrounds another selection
16354    _ = leader.update(cx, |leader, window, cx| {
16355        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16356    });
16357    follower
16358        .update(cx, |follower, window, cx| {
16359            follower.apply_update_proto(
16360                &project,
16361                pending_update.borrow_mut().take().unwrap(),
16362                window,
16363                cx,
16364            )
16365        })
16366        .unwrap()
16367        .await
16368        .unwrap();
16369    _ = follower.update(cx, |follower, _, cx| {
16370        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16371    });
16372
16373    // Scrolling locally breaks the follow
16374    _ = follower.update(cx, |follower, window, cx| {
16375        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16376        follower.set_scroll_anchor(
16377            ScrollAnchor {
16378                anchor: top_anchor,
16379                offset: gpui::Point::new(0.0, 0.5),
16380            },
16381            window,
16382            cx,
16383        );
16384    });
16385    assert!(!(*is_still_following.borrow()));
16386}
16387
16388#[gpui::test]
16389async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16390    init_test(cx, |_| {});
16391
16392    let fs = FakeFs::new(cx.executor());
16393    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16394    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16395    let pane = workspace
16396        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16397        .unwrap();
16398
16399    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16400
16401    let leader = pane.update_in(cx, |_, window, cx| {
16402        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16403        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16404    });
16405
16406    // Start following the editor when it has no excerpts.
16407    let mut state_message =
16408        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16409    let workspace_entity = workspace.root(cx).unwrap();
16410    let follower_1 = cx
16411        .update_window(*workspace.deref(), |_, window, cx| {
16412            Editor::from_state_proto(
16413                workspace_entity,
16414                ViewId {
16415                    creator: CollaboratorId::PeerId(PeerId::default()),
16416                    id: 0,
16417                },
16418                &mut state_message,
16419                window,
16420                cx,
16421            )
16422        })
16423        .unwrap()
16424        .unwrap()
16425        .await
16426        .unwrap();
16427
16428    let update_message = Rc::new(RefCell::new(None));
16429    follower_1.update_in(cx, {
16430        let update = update_message.clone();
16431        |_, window, cx| {
16432            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16433                leader.read(cx).add_event_to_update_proto(
16434                    event,
16435                    &mut update.borrow_mut(),
16436                    window,
16437                    cx,
16438                );
16439            })
16440            .detach();
16441        }
16442    });
16443
16444    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16445        (
16446            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16447            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16448        )
16449    });
16450
16451    // Insert some excerpts.
16452    leader.update(cx, |leader, cx| {
16453        leader.buffer.update(cx, |multibuffer, cx| {
16454            multibuffer.set_excerpts_for_path(
16455                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
16456                buffer_1.clone(),
16457                vec![
16458                    Point::row_range(0..3),
16459                    Point::row_range(1..6),
16460                    Point::row_range(12..15),
16461                ],
16462                0,
16463                cx,
16464            );
16465            multibuffer.set_excerpts_for_path(
16466                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
16467                buffer_2.clone(),
16468                vec![Point::row_range(0..6), Point::row_range(8..12)],
16469                0,
16470                cx,
16471            );
16472        });
16473    });
16474
16475    // Apply the update of adding the excerpts.
16476    follower_1
16477        .update_in(cx, |follower, window, cx| {
16478            follower.apply_update_proto(
16479                &project,
16480                update_message.borrow().clone().unwrap(),
16481                window,
16482                cx,
16483            )
16484        })
16485        .await
16486        .unwrap();
16487    assert_eq!(
16488        follower_1.update(cx, |editor, cx| editor.text(cx)),
16489        leader.update(cx, |editor, cx| editor.text(cx))
16490    );
16491    update_message.borrow_mut().take();
16492
16493    // Start following separately after it already has excerpts.
16494    let mut state_message =
16495        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16496    let workspace_entity = workspace.root(cx).unwrap();
16497    let follower_2 = cx
16498        .update_window(*workspace.deref(), |_, window, cx| {
16499            Editor::from_state_proto(
16500                workspace_entity,
16501                ViewId {
16502                    creator: CollaboratorId::PeerId(PeerId::default()),
16503                    id: 0,
16504                },
16505                &mut state_message,
16506                window,
16507                cx,
16508            )
16509        })
16510        .unwrap()
16511        .unwrap()
16512        .await
16513        .unwrap();
16514    assert_eq!(
16515        follower_2.update(cx, |editor, cx| editor.text(cx)),
16516        leader.update(cx, |editor, cx| editor.text(cx))
16517    );
16518
16519    // Remove some excerpts.
16520    leader.update(cx, |leader, cx| {
16521        leader.buffer.update(cx, |multibuffer, cx| {
16522            let excerpt_ids = multibuffer.excerpt_ids();
16523            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16524            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16525        });
16526    });
16527
16528    // Apply the update of removing the excerpts.
16529    follower_1
16530        .update_in(cx, |follower, window, cx| {
16531            follower.apply_update_proto(
16532                &project,
16533                update_message.borrow().clone().unwrap(),
16534                window,
16535                cx,
16536            )
16537        })
16538        .await
16539        .unwrap();
16540    follower_2
16541        .update_in(cx, |follower, window, cx| {
16542            follower.apply_update_proto(
16543                &project,
16544                update_message.borrow().clone().unwrap(),
16545                window,
16546                cx,
16547            )
16548        })
16549        .await
16550        .unwrap();
16551    update_message.borrow_mut().take();
16552    assert_eq!(
16553        follower_1.update(cx, |editor, cx| editor.text(cx)),
16554        leader.update(cx, |editor, cx| editor.text(cx))
16555    );
16556}
16557
16558#[gpui::test]
16559async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16560    init_test(cx, |_| {});
16561
16562    let mut cx = EditorTestContext::new(cx).await;
16563    let lsp_store =
16564        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16565
16566    cx.set_state(indoc! {"
16567        ˇfn func(abc def: i32) -> u32 {
16568        }
16569    "});
16570
16571    cx.update(|_, cx| {
16572        lsp_store.update(cx, |lsp_store, cx| {
16573            lsp_store
16574                .update_diagnostics(
16575                    LanguageServerId(0),
16576                    lsp::PublishDiagnosticsParams {
16577                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16578                        version: None,
16579                        diagnostics: vec![
16580                            lsp::Diagnostic {
16581                                range: lsp::Range::new(
16582                                    lsp::Position::new(0, 11),
16583                                    lsp::Position::new(0, 12),
16584                                ),
16585                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16586                                ..Default::default()
16587                            },
16588                            lsp::Diagnostic {
16589                                range: lsp::Range::new(
16590                                    lsp::Position::new(0, 12),
16591                                    lsp::Position::new(0, 15),
16592                                ),
16593                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16594                                ..Default::default()
16595                            },
16596                            lsp::Diagnostic {
16597                                range: lsp::Range::new(
16598                                    lsp::Position::new(0, 25),
16599                                    lsp::Position::new(0, 28),
16600                                ),
16601                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16602                                ..Default::default()
16603                            },
16604                        ],
16605                    },
16606                    None,
16607                    DiagnosticSourceKind::Pushed,
16608                    &[],
16609                    cx,
16610                )
16611                .unwrap()
16612        });
16613    });
16614
16615    executor.run_until_parked();
16616
16617    cx.update_editor(|editor, window, cx| {
16618        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16619    });
16620
16621    cx.assert_editor_state(indoc! {"
16622        fn func(abc def: i32) -> ˇu32 {
16623        }
16624    "});
16625
16626    cx.update_editor(|editor, window, cx| {
16627        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16628    });
16629
16630    cx.assert_editor_state(indoc! {"
16631        fn func(abc ˇdef: i32) -> u32 {
16632        }
16633    "});
16634
16635    cx.update_editor(|editor, window, cx| {
16636        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16637    });
16638
16639    cx.assert_editor_state(indoc! {"
16640        fn func(abcˇ def: i32) -> u32 {
16641        }
16642    "});
16643
16644    cx.update_editor(|editor, window, cx| {
16645        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16646    });
16647
16648    cx.assert_editor_state(indoc! {"
16649        fn func(abc def: i32) -> ˇu32 {
16650        }
16651    "});
16652}
16653
16654#[gpui::test]
16655async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16656    init_test(cx, |_| {});
16657
16658    let mut cx = EditorTestContext::new(cx).await;
16659
16660    let diff_base = r#"
16661        use some::mod;
16662
16663        const A: u32 = 42;
16664
16665        fn main() {
16666            println!("hello");
16667
16668            println!("world");
16669        }
16670        "#
16671    .unindent();
16672
16673    // Edits are modified, removed, modified, added
16674    cx.set_state(
16675        &r#"
16676        use some::modified;
16677
16678        ˇ
16679        fn main() {
16680            println!("hello there");
16681
16682            println!("around the");
16683            println!("world");
16684        }
16685        "#
16686        .unindent(),
16687    );
16688
16689    cx.set_head_text(&diff_base);
16690    executor.run_until_parked();
16691
16692    cx.update_editor(|editor, window, cx| {
16693        //Wrap around the bottom of the buffer
16694        for _ in 0..3 {
16695            editor.go_to_next_hunk(&GoToHunk, window, cx);
16696        }
16697    });
16698
16699    cx.assert_editor_state(
16700        &r#"
16701        ˇuse some::modified;
16702
16703
16704        fn main() {
16705            println!("hello there");
16706
16707            println!("around the");
16708            println!("world");
16709        }
16710        "#
16711        .unindent(),
16712    );
16713
16714    cx.update_editor(|editor, window, cx| {
16715        //Wrap around the top of the buffer
16716        for _ in 0..2 {
16717            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16718        }
16719    });
16720
16721    cx.assert_editor_state(
16722        &r#"
16723        use some::modified;
16724
16725
16726        fn main() {
16727        ˇ    println!("hello there");
16728
16729            println!("around the");
16730            println!("world");
16731        }
16732        "#
16733        .unindent(),
16734    );
16735
16736    cx.update_editor(|editor, window, cx| {
16737        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16738    });
16739
16740    cx.assert_editor_state(
16741        &r#"
16742        use some::modified;
16743
16744        ˇ
16745        fn main() {
16746            println!("hello there");
16747
16748            println!("around the");
16749            println!("world");
16750        }
16751        "#
16752        .unindent(),
16753    );
16754
16755    cx.update_editor(|editor, window, cx| {
16756        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16757    });
16758
16759    cx.assert_editor_state(
16760        &r#"
16761        ˇuse some::modified;
16762
16763
16764        fn main() {
16765            println!("hello there");
16766
16767            println!("around the");
16768            println!("world");
16769        }
16770        "#
16771        .unindent(),
16772    );
16773
16774    cx.update_editor(|editor, window, cx| {
16775        for _ in 0..2 {
16776            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16777        }
16778    });
16779
16780    cx.assert_editor_state(
16781        &r#"
16782        use some::modified;
16783
16784
16785        fn main() {
16786        ˇ    println!("hello there");
16787
16788            println!("around the");
16789            println!("world");
16790        }
16791        "#
16792        .unindent(),
16793    );
16794
16795    cx.update_editor(|editor, window, cx| {
16796        editor.fold(&Fold, window, cx);
16797    });
16798
16799    cx.update_editor(|editor, window, cx| {
16800        editor.go_to_next_hunk(&GoToHunk, window, cx);
16801    });
16802
16803    cx.assert_editor_state(
16804        &r#"
16805        ˇuse some::modified;
16806
16807
16808        fn main() {
16809            println!("hello there");
16810
16811            println!("around the");
16812            println!("world");
16813        }
16814        "#
16815        .unindent(),
16816    );
16817}
16818
16819#[test]
16820fn test_split_words() {
16821    fn split(text: &str) -> Vec<&str> {
16822        split_words(text).collect()
16823    }
16824
16825    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16826    assert_eq!(split("hello_world"), &["hello_", "world"]);
16827    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16828    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16829    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16830    assert_eq!(split("helloworld"), &["helloworld"]);
16831
16832    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16833}
16834
16835#[gpui::test]
16836async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16837    init_test(cx, |_| {});
16838
16839    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16840    let mut assert = |before, after| {
16841        let _state_context = cx.set_state(before);
16842        cx.run_until_parked();
16843        cx.update_editor(|editor, window, cx| {
16844            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16845        });
16846        cx.run_until_parked();
16847        cx.assert_editor_state(after);
16848    };
16849
16850    // Outside bracket jumps to outside of matching bracket
16851    assert("console.logˇ(var);", "console.log(var)ˇ;");
16852    assert("console.log(var)ˇ;", "console.logˇ(var);");
16853
16854    // Inside bracket jumps to inside of matching bracket
16855    assert("console.log(ˇvar);", "console.log(varˇ);");
16856    assert("console.log(varˇ);", "console.log(ˇvar);");
16857
16858    // When outside a bracket and inside, favor jumping to the inside bracket
16859    assert(
16860        "console.log('foo', [1, 2, 3]ˇ);",
16861        "console.log(ˇ'foo', [1, 2, 3]);",
16862    );
16863    assert(
16864        "console.log(ˇ'foo', [1, 2, 3]);",
16865        "console.log('foo', [1, 2, 3]ˇ);",
16866    );
16867
16868    // Bias forward if two options are equally likely
16869    assert(
16870        "let result = curried_fun()ˇ();",
16871        "let result = curried_fun()()ˇ;",
16872    );
16873
16874    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16875    assert(
16876        indoc! {"
16877            function test() {
16878                console.log('test')ˇ
16879            }"},
16880        indoc! {"
16881            function test() {
16882                console.logˇ('test')
16883            }"},
16884    );
16885}
16886
16887#[gpui::test]
16888async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16889    init_test(cx, |_| {});
16890
16891    let fs = FakeFs::new(cx.executor());
16892    fs.insert_tree(
16893        path!("/a"),
16894        json!({
16895            "main.rs": "fn main() { let a = 5; }",
16896            "other.rs": "// Test file",
16897        }),
16898    )
16899    .await;
16900    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16901
16902    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16903    language_registry.add(Arc::new(Language::new(
16904        LanguageConfig {
16905            name: "Rust".into(),
16906            matcher: LanguageMatcher {
16907                path_suffixes: vec!["rs".to_string()],
16908                ..Default::default()
16909            },
16910            brackets: BracketPairConfig {
16911                pairs: vec![BracketPair {
16912                    start: "{".to_string(),
16913                    end: "}".to_string(),
16914                    close: true,
16915                    surround: true,
16916                    newline: true,
16917                }],
16918                disabled_scopes_by_bracket_ix: Vec::new(),
16919            },
16920            ..Default::default()
16921        },
16922        Some(tree_sitter_rust::LANGUAGE.into()),
16923    )));
16924    let mut fake_servers = language_registry.register_fake_lsp(
16925        "Rust",
16926        FakeLspAdapter {
16927            capabilities: lsp::ServerCapabilities {
16928                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16929                    first_trigger_character: "{".to_string(),
16930                    more_trigger_character: None,
16931                }),
16932                ..Default::default()
16933            },
16934            ..Default::default()
16935        },
16936    );
16937
16938    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16939
16940    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16941
16942    let worktree_id = workspace
16943        .update(cx, |workspace, _, cx| {
16944            workspace.project().update(cx, |project, cx| {
16945                project.worktrees(cx).next().unwrap().read(cx).id()
16946            })
16947        })
16948        .unwrap();
16949
16950    let buffer = project
16951        .update(cx, |project, cx| {
16952            project.open_local_buffer(path!("/a/main.rs"), cx)
16953        })
16954        .await
16955        .unwrap();
16956    let editor_handle = workspace
16957        .update(cx, |workspace, window, cx| {
16958            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
16959        })
16960        .unwrap()
16961        .await
16962        .unwrap()
16963        .downcast::<Editor>()
16964        .unwrap();
16965
16966    cx.executor().start_waiting();
16967    let fake_server = fake_servers.next().await.unwrap();
16968
16969    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
16970        |params, _| async move {
16971            assert_eq!(
16972                params.text_document_position.text_document.uri,
16973                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
16974            );
16975            assert_eq!(
16976                params.text_document_position.position,
16977                lsp::Position::new(0, 21),
16978            );
16979
16980            Ok(Some(vec![lsp::TextEdit {
16981                new_text: "]".to_string(),
16982                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16983            }]))
16984        },
16985    );
16986
16987    editor_handle.update_in(cx, |editor, window, cx| {
16988        window.focus(&editor.focus_handle(cx));
16989        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16990            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
16991        });
16992        editor.handle_input("{", window, cx);
16993    });
16994
16995    cx.executor().run_until_parked();
16996
16997    buffer.update(cx, |buffer, _| {
16998        assert_eq!(
16999            buffer.text(),
17000            "fn main() { let a = {5}; }",
17001            "No extra braces from on type formatting should appear in the buffer"
17002        )
17003    });
17004}
17005
17006#[gpui::test(iterations = 20, seeds(31))]
17007async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17008    init_test(cx, |_| {});
17009
17010    let mut cx = EditorLspTestContext::new_rust(
17011        lsp::ServerCapabilities {
17012            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17013                first_trigger_character: ".".to_string(),
17014                more_trigger_character: None,
17015            }),
17016            ..Default::default()
17017        },
17018        cx,
17019    )
17020    .await;
17021
17022    cx.update_buffer(|buffer, _| {
17023        // This causes autoindent to be async.
17024        buffer.set_sync_parse_timeout(Duration::ZERO)
17025    });
17026
17027    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17028    cx.simulate_keystroke("\n");
17029    cx.run_until_parked();
17030
17031    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17032    let mut request =
17033        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17034            let buffer_cloned = buffer_cloned.clone();
17035            async move {
17036                buffer_cloned.update(&mut cx, |buffer, _| {
17037                    assert_eq!(
17038                        buffer.text(),
17039                        "fn c() {\n    d()\n        .\n}\n",
17040                        "OnTypeFormatting should triggered after autoindent applied"
17041                    )
17042                })?;
17043
17044                Ok(Some(vec![]))
17045            }
17046        });
17047
17048    cx.simulate_keystroke(".");
17049    cx.run_until_parked();
17050
17051    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17052    assert!(request.next().await.is_some());
17053    request.close();
17054    assert!(request.next().await.is_none());
17055}
17056
17057#[gpui::test]
17058async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17059    init_test(cx, |_| {});
17060
17061    let fs = FakeFs::new(cx.executor());
17062    fs.insert_tree(
17063        path!("/a"),
17064        json!({
17065            "main.rs": "fn main() { let a = 5; }",
17066            "other.rs": "// Test file",
17067        }),
17068    )
17069    .await;
17070
17071    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17072
17073    let server_restarts = Arc::new(AtomicUsize::new(0));
17074    let closure_restarts = Arc::clone(&server_restarts);
17075    let language_server_name = "test language server";
17076    let language_name: LanguageName = "Rust".into();
17077
17078    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17079    language_registry.add(Arc::new(Language::new(
17080        LanguageConfig {
17081            name: language_name.clone(),
17082            matcher: LanguageMatcher {
17083                path_suffixes: vec!["rs".to_string()],
17084                ..Default::default()
17085            },
17086            ..Default::default()
17087        },
17088        Some(tree_sitter_rust::LANGUAGE.into()),
17089    )));
17090    let mut fake_servers = language_registry.register_fake_lsp(
17091        "Rust",
17092        FakeLspAdapter {
17093            name: language_server_name,
17094            initialization_options: Some(json!({
17095                "testOptionValue": true
17096            })),
17097            initializer: Some(Box::new(move |fake_server| {
17098                let task_restarts = Arc::clone(&closure_restarts);
17099                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17100                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17101                    futures::future::ready(Ok(()))
17102                });
17103            })),
17104            ..Default::default()
17105        },
17106    );
17107
17108    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17109    let _buffer = project
17110        .update(cx, |project, cx| {
17111            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17112        })
17113        .await
17114        .unwrap();
17115    let _fake_server = fake_servers.next().await.unwrap();
17116    update_test_language_settings(cx, |language_settings| {
17117        language_settings.languages.0.insert(
17118            language_name.clone().0,
17119            LanguageSettingsContent {
17120                tab_size: NonZeroU32::new(8),
17121                ..Default::default()
17122            },
17123        );
17124    });
17125    cx.executor().run_until_parked();
17126    assert_eq!(
17127        server_restarts.load(atomic::Ordering::Acquire),
17128        0,
17129        "Should not restart LSP server on an unrelated change"
17130    );
17131
17132    update_test_project_settings(cx, |project_settings| {
17133        project_settings.lsp.insert(
17134            "Some other server name".into(),
17135            LspSettings {
17136                binary: None,
17137                settings: None,
17138                initialization_options: Some(json!({
17139                    "some other init value": false
17140                })),
17141                enable_lsp_tasks: false,
17142                fetch: None,
17143            },
17144        );
17145    });
17146    cx.executor().run_until_parked();
17147    assert_eq!(
17148        server_restarts.load(atomic::Ordering::Acquire),
17149        0,
17150        "Should not restart LSP server on an unrelated LSP settings change"
17151    );
17152
17153    update_test_project_settings(cx, |project_settings| {
17154        project_settings.lsp.insert(
17155            language_server_name.into(),
17156            LspSettings {
17157                binary: None,
17158                settings: None,
17159                initialization_options: Some(json!({
17160                    "anotherInitValue": false
17161                })),
17162                enable_lsp_tasks: false,
17163                fetch: None,
17164            },
17165        );
17166    });
17167    cx.executor().run_until_parked();
17168    assert_eq!(
17169        server_restarts.load(atomic::Ordering::Acquire),
17170        1,
17171        "Should restart LSP server on a related LSP settings change"
17172    );
17173
17174    update_test_project_settings(cx, |project_settings| {
17175        project_settings.lsp.insert(
17176            language_server_name.into(),
17177            LspSettings {
17178                binary: None,
17179                settings: None,
17180                initialization_options: Some(json!({
17181                    "anotherInitValue": false
17182                })),
17183                enable_lsp_tasks: false,
17184                fetch: None,
17185            },
17186        );
17187    });
17188    cx.executor().run_until_parked();
17189    assert_eq!(
17190        server_restarts.load(atomic::Ordering::Acquire),
17191        1,
17192        "Should not restart LSP server on a related LSP settings change that is the same"
17193    );
17194
17195    update_test_project_settings(cx, |project_settings| {
17196        project_settings.lsp.insert(
17197            language_server_name.into(),
17198            LspSettings {
17199                binary: None,
17200                settings: None,
17201                initialization_options: None,
17202                enable_lsp_tasks: false,
17203                fetch: None,
17204            },
17205        );
17206    });
17207    cx.executor().run_until_parked();
17208    assert_eq!(
17209        server_restarts.load(atomic::Ordering::Acquire),
17210        2,
17211        "Should restart LSP server on another related LSP settings change"
17212    );
17213}
17214
17215#[gpui::test]
17216async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17217    init_test(cx, |_| {});
17218
17219    let mut cx = EditorLspTestContext::new_rust(
17220        lsp::ServerCapabilities {
17221            completion_provider: Some(lsp::CompletionOptions {
17222                trigger_characters: Some(vec![".".to_string()]),
17223                resolve_provider: Some(true),
17224                ..Default::default()
17225            }),
17226            ..Default::default()
17227        },
17228        cx,
17229    )
17230    .await;
17231
17232    cx.set_state("fn main() { let a = 2ˇ; }");
17233    cx.simulate_keystroke(".");
17234    let completion_item = lsp::CompletionItem {
17235        label: "some".into(),
17236        kind: Some(lsp::CompletionItemKind::SNIPPET),
17237        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17238        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17239            kind: lsp::MarkupKind::Markdown,
17240            value: "```rust\nSome(2)\n```".to_string(),
17241        })),
17242        deprecated: Some(false),
17243        sort_text: Some("fffffff2".to_string()),
17244        filter_text: Some("some".to_string()),
17245        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17246        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17247            range: lsp::Range {
17248                start: lsp::Position {
17249                    line: 0,
17250                    character: 22,
17251                },
17252                end: lsp::Position {
17253                    line: 0,
17254                    character: 22,
17255                },
17256            },
17257            new_text: "Some(2)".to_string(),
17258        })),
17259        additional_text_edits: Some(vec![lsp::TextEdit {
17260            range: lsp::Range {
17261                start: lsp::Position {
17262                    line: 0,
17263                    character: 20,
17264                },
17265                end: lsp::Position {
17266                    line: 0,
17267                    character: 22,
17268                },
17269            },
17270            new_text: "".to_string(),
17271        }]),
17272        ..Default::default()
17273    };
17274
17275    let closure_completion_item = completion_item.clone();
17276    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17277        let task_completion_item = closure_completion_item.clone();
17278        async move {
17279            Ok(Some(lsp::CompletionResponse::Array(vec![
17280                task_completion_item,
17281            ])))
17282        }
17283    });
17284
17285    request.next().await;
17286
17287    cx.condition(|editor, _| editor.context_menu_visible())
17288        .await;
17289    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17290        editor
17291            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17292            .unwrap()
17293    });
17294    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17295
17296    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17297        let task_completion_item = completion_item.clone();
17298        async move { Ok(task_completion_item) }
17299    })
17300    .next()
17301    .await
17302    .unwrap();
17303    apply_additional_edits.await.unwrap();
17304    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17305}
17306
17307#[gpui::test]
17308async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17309    init_test(cx, |_| {});
17310
17311    let mut cx = EditorLspTestContext::new_rust(
17312        lsp::ServerCapabilities {
17313            completion_provider: Some(lsp::CompletionOptions {
17314                trigger_characters: Some(vec![".".to_string()]),
17315                resolve_provider: Some(true),
17316                ..Default::default()
17317            }),
17318            ..Default::default()
17319        },
17320        cx,
17321    )
17322    .await;
17323
17324    cx.set_state("fn main() { let a = 2ˇ; }");
17325    cx.simulate_keystroke(".");
17326
17327    let item1 = lsp::CompletionItem {
17328        label: "method id()".to_string(),
17329        filter_text: Some("id".to_string()),
17330        detail: None,
17331        documentation: None,
17332        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17333            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17334            new_text: ".id".to_string(),
17335        })),
17336        ..lsp::CompletionItem::default()
17337    };
17338
17339    let item2 = lsp::CompletionItem {
17340        label: "other".to_string(),
17341        filter_text: Some("other".to_string()),
17342        detail: None,
17343        documentation: None,
17344        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17345            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17346            new_text: ".other".to_string(),
17347        })),
17348        ..lsp::CompletionItem::default()
17349    };
17350
17351    let item1 = item1.clone();
17352    cx.set_request_handler::<lsp::request::Completion, _, _>({
17353        let item1 = item1.clone();
17354        move |_, _, _| {
17355            let item1 = item1.clone();
17356            let item2 = item2.clone();
17357            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17358        }
17359    })
17360    .next()
17361    .await;
17362
17363    cx.condition(|editor, _| editor.context_menu_visible())
17364        .await;
17365    cx.update_editor(|editor, _, _| {
17366        let context_menu = editor.context_menu.borrow_mut();
17367        let context_menu = context_menu
17368            .as_ref()
17369            .expect("Should have the context menu deployed");
17370        match context_menu {
17371            CodeContextMenu::Completions(completions_menu) => {
17372                let completions = completions_menu.completions.borrow_mut();
17373                assert_eq!(
17374                    completions
17375                        .iter()
17376                        .map(|completion| &completion.label.text)
17377                        .collect::<Vec<_>>(),
17378                    vec!["method id()", "other"]
17379                )
17380            }
17381            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17382        }
17383    });
17384
17385    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17386        let item1 = item1.clone();
17387        move |_, item_to_resolve, _| {
17388            let item1 = item1.clone();
17389            async move {
17390                if item1 == item_to_resolve {
17391                    Ok(lsp::CompletionItem {
17392                        label: "method id()".to_string(),
17393                        filter_text: Some("id".to_string()),
17394                        detail: Some("Now resolved!".to_string()),
17395                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17396                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17397                            range: lsp::Range::new(
17398                                lsp::Position::new(0, 22),
17399                                lsp::Position::new(0, 22),
17400                            ),
17401                            new_text: ".id".to_string(),
17402                        })),
17403                        ..lsp::CompletionItem::default()
17404                    })
17405                } else {
17406                    Ok(item_to_resolve)
17407                }
17408            }
17409        }
17410    })
17411    .next()
17412    .await
17413    .unwrap();
17414    cx.run_until_parked();
17415
17416    cx.update_editor(|editor, window, cx| {
17417        editor.context_menu_next(&Default::default(), window, cx);
17418    });
17419
17420    cx.update_editor(|editor, _, _| {
17421        let context_menu = editor.context_menu.borrow_mut();
17422        let context_menu = context_menu
17423            .as_ref()
17424            .expect("Should have the context menu deployed");
17425        match context_menu {
17426            CodeContextMenu::Completions(completions_menu) => {
17427                let completions = completions_menu.completions.borrow_mut();
17428                assert_eq!(
17429                    completions
17430                        .iter()
17431                        .map(|completion| &completion.label.text)
17432                        .collect::<Vec<_>>(),
17433                    vec!["method id() Now resolved!", "other"],
17434                    "Should update first completion label, but not second as the filter text did not match."
17435                );
17436            }
17437            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17438        }
17439    });
17440}
17441
17442#[gpui::test]
17443async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17444    init_test(cx, |_| {});
17445    let mut cx = EditorLspTestContext::new_rust(
17446        lsp::ServerCapabilities {
17447            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17448            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17449            completion_provider: Some(lsp::CompletionOptions {
17450                resolve_provider: Some(true),
17451                ..Default::default()
17452            }),
17453            ..Default::default()
17454        },
17455        cx,
17456    )
17457    .await;
17458    cx.set_state(indoc! {"
17459        struct TestStruct {
17460            field: i32
17461        }
17462
17463        fn mainˇ() {
17464            let unused_var = 42;
17465            let test_struct = TestStruct { field: 42 };
17466        }
17467    "});
17468    let symbol_range = cx.lsp_range(indoc! {"
17469        struct TestStruct {
17470            field: i32
17471        }
17472
17473        «fn main»() {
17474            let unused_var = 42;
17475            let test_struct = TestStruct { field: 42 };
17476        }
17477    "});
17478    let mut hover_requests =
17479        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17480            Ok(Some(lsp::Hover {
17481                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17482                    kind: lsp::MarkupKind::Markdown,
17483                    value: "Function documentation".to_string(),
17484                }),
17485                range: Some(symbol_range),
17486            }))
17487        });
17488
17489    // Case 1: Test that code action menu hide hover popover
17490    cx.dispatch_action(Hover);
17491    hover_requests.next().await;
17492    cx.condition(|editor, _| editor.hover_state.visible()).await;
17493    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17494        move |_, _, _| async move {
17495            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17496                lsp::CodeAction {
17497                    title: "Remove unused variable".to_string(),
17498                    kind: Some(CodeActionKind::QUICKFIX),
17499                    edit: Some(lsp::WorkspaceEdit {
17500                        changes: Some(
17501                            [(
17502                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17503                                vec![lsp::TextEdit {
17504                                    range: lsp::Range::new(
17505                                        lsp::Position::new(5, 4),
17506                                        lsp::Position::new(5, 27),
17507                                    ),
17508                                    new_text: "".to_string(),
17509                                }],
17510                            )]
17511                            .into_iter()
17512                            .collect(),
17513                        ),
17514                        ..Default::default()
17515                    }),
17516                    ..Default::default()
17517                },
17518            )]))
17519        },
17520    );
17521    cx.update_editor(|editor, window, cx| {
17522        editor.toggle_code_actions(
17523            &ToggleCodeActions {
17524                deployed_from: None,
17525                quick_launch: false,
17526            },
17527            window,
17528            cx,
17529        );
17530    });
17531    code_action_requests.next().await;
17532    cx.run_until_parked();
17533    cx.condition(|editor, _| editor.context_menu_visible())
17534        .await;
17535    cx.update_editor(|editor, _, _| {
17536        assert!(
17537            !editor.hover_state.visible(),
17538            "Hover popover should be hidden when code action menu is shown"
17539        );
17540        // Hide code actions
17541        editor.context_menu.take();
17542    });
17543
17544    // Case 2: Test that code completions hide hover popover
17545    cx.dispatch_action(Hover);
17546    hover_requests.next().await;
17547    cx.condition(|editor, _| editor.hover_state.visible()).await;
17548    let counter = Arc::new(AtomicUsize::new(0));
17549    let mut completion_requests =
17550        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17551            let counter = counter.clone();
17552            async move {
17553                counter.fetch_add(1, atomic::Ordering::Release);
17554                Ok(Some(lsp::CompletionResponse::Array(vec![
17555                    lsp::CompletionItem {
17556                        label: "main".into(),
17557                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17558                        detail: Some("() -> ()".to_string()),
17559                        ..Default::default()
17560                    },
17561                    lsp::CompletionItem {
17562                        label: "TestStruct".into(),
17563                        kind: Some(lsp::CompletionItemKind::STRUCT),
17564                        detail: Some("struct TestStruct".to_string()),
17565                        ..Default::default()
17566                    },
17567                ])))
17568            }
17569        });
17570    cx.update_editor(|editor, window, cx| {
17571        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17572    });
17573    completion_requests.next().await;
17574    cx.condition(|editor, _| editor.context_menu_visible())
17575        .await;
17576    cx.update_editor(|editor, _, _| {
17577        assert!(
17578            !editor.hover_state.visible(),
17579            "Hover popover should be hidden when completion menu is shown"
17580        );
17581    });
17582}
17583
17584#[gpui::test]
17585async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17586    init_test(cx, |_| {});
17587
17588    let mut cx = EditorLspTestContext::new_rust(
17589        lsp::ServerCapabilities {
17590            completion_provider: Some(lsp::CompletionOptions {
17591                trigger_characters: Some(vec![".".to_string()]),
17592                resolve_provider: Some(true),
17593                ..Default::default()
17594            }),
17595            ..Default::default()
17596        },
17597        cx,
17598    )
17599    .await;
17600
17601    cx.set_state("fn main() { let a = 2ˇ; }");
17602    cx.simulate_keystroke(".");
17603
17604    let unresolved_item_1 = lsp::CompletionItem {
17605        label: "id".to_string(),
17606        filter_text: Some("id".to_string()),
17607        detail: None,
17608        documentation: None,
17609        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17610            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17611            new_text: ".id".to_string(),
17612        })),
17613        ..lsp::CompletionItem::default()
17614    };
17615    let resolved_item_1 = lsp::CompletionItem {
17616        additional_text_edits: Some(vec![lsp::TextEdit {
17617            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17618            new_text: "!!".to_string(),
17619        }]),
17620        ..unresolved_item_1.clone()
17621    };
17622    let unresolved_item_2 = lsp::CompletionItem {
17623        label: "other".to_string(),
17624        filter_text: Some("other".to_string()),
17625        detail: None,
17626        documentation: None,
17627        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17628            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17629            new_text: ".other".to_string(),
17630        })),
17631        ..lsp::CompletionItem::default()
17632    };
17633    let resolved_item_2 = lsp::CompletionItem {
17634        additional_text_edits: Some(vec![lsp::TextEdit {
17635            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17636            new_text: "??".to_string(),
17637        }]),
17638        ..unresolved_item_2.clone()
17639    };
17640
17641    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17642    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17643    cx.lsp
17644        .server
17645        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17646            let unresolved_item_1 = unresolved_item_1.clone();
17647            let resolved_item_1 = resolved_item_1.clone();
17648            let unresolved_item_2 = unresolved_item_2.clone();
17649            let resolved_item_2 = resolved_item_2.clone();
17650            let resolve_requests_1 = resolve_requests_1.clone();
17651            let resolve_requests_2 = resolve_requests_2.clone();
17652            move |unresolved_request, _| {
17653                let unresolved_item_1 = unresolved_item_1.clone();
17654                let resolved_item_1 = resolved_item_1.clone();
17655                let unresolved_item_2 = unresolved_item_2.clone();
17656                let resolved_item_2 = resolved_item_2.clone();
17657                let resolve_requests_1 = resolve_requests_1.clone();
17658                let resolve_requests_2 = resolve_requests_2.clone();
17659                async move {
17660                    if unresolved_request == unresolved_item_1 {
17661                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17662                        Ok(resolved_item_1.clone())
17663                    } else if unresolved_request == unresolved_item_2 {
17664                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17665                        Ok(resolved_item_2.clone())
17666                    } else {
17667                        panic!("Unexpected completion item {unresolved_request:?}")
17668                    }
17669                }
17670            }
17671        })
17672        .detach();
17673
17674    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17675        let unresolved_item_1 = unresolved_item_1.clone();
17676        let unresolved_item_2 = unresolved_item_2.clone();
17677        async move {
17678            Ok(Some(lsp::CompletionResponse::Array(vec![
17679                unresolved_item_1,
17680                unresolved_item_2,
17681            ])))
17682        }
17683    })
17684    .next()
17685    .await;
17686
17687    cx.condition(|editor, _| editor.context_menu_visible())
17688        .await;
17689    cx.update_editor(|editor, _, _| {
17690        let context_menu = editor.context_menu.borrow_mut();
17691        let context_menu = context_menu
17692            .as_ref()
17693            .expect("Should have the context menu deployed");
17694        match context_menu {
17695            CodeContextMenu::Completions(completions_menu) => {
17696                let completions = completions_menu.completions.borrow_mut();
17697                assert_eq!(
17698                    completions
17699                        .iter()
17700                        .map(|completion| &completion.label.text)
17701                        .collect::<Vec<_>>(),
17702                    vec!["id", "other"]
17703                )
17704            }
17705            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17706        }
17707    });
17708    cx.run_until_parked();
17709
17710    cx.update_editor(|editor, window, cx| {
17711        editor.context_menu_next(&ContextMenuNext, window, cx);
17712    });
17713    cx.run_until_parked();
17714    cx.update_editor(|editor, window, cx| {
17715        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17716    });
17717    cx.run_until_parked();
17718    cx.update_editor(|editor, window, cx| {
17719        editor.context_menu_next(&ContextMenuNext, window, cx);
17720    });
17721    cx.run_until_parked();
17722    cx.update_editor(|editor, window, cx| {
17723        editor
17724            .compose_completion(&ComposeCompletion::default(), window, cx)
17725            .expect("No task returned")
17726    })
17727    .await
17728    .expect("Completion failed");
17729    cx.run_until_parked();
17730
17731    cx.update_editor(|editor, _, cx| {
17732        assert_eq!(
17733            resolve_requests_1.load(atomic::Ordering::Acquire),
17734            1,
17735            "Should always resolve once despite multiple selections"
17736        );
17737        assert_eq!(
17738            resolve_requests_2.load(atomic::Ordering::Acquire),
17739            1,
17740            "Should always resolve once after multiple selections and applying the completion"
17741        );
17742        assert_eq!(
17743            editor.text(cx),
17744            "fn main() { let a = ??.other; }",
17745            "Should use resolved data when applying the completion"
17746        );
17747    });
17748}
17749
17750#[gpui::test]
17751async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17752    init_test(cx, |_| {});
17753
17754    let item_0 = lsp::CompletionItem {
17755        label: "abs".into(),
17756        insert_text: Some("abs".into()),
17757        data: Some(json!({ "very": "special"})),
17758        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17759        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17760            lsp::InsertReplaceEdit {
17761                new_text: "abs".to_string(),
17762                insert: lsp::Range::default(),
17763                replace: lsp::Range::default(),
17764            },
17765        )),
17766        ..lsp::CompletionItem::default()
17767    };
17768    let items = iter::once(item_0.clone())
17769        .chain((11..51).map(|i| lsp::CompletionItem {
17770            label: format!("item_{}", i),
17771            insert_text: Some(format!("item_{}", i)),
17772            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17773            ..lsp::CompletionItem::default()
17774        }))
17775        .collect::<Vec<_>>();
17776
17777    let default_commit_characters = vec!["?".to_string()];
17778    let default_data = json!({ "default": "data"});
17779    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17780    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17781    let default_edit_range = lsp::Range {
17782        start: lsp::Position {
17783            line: 0,
17784            character: 5,
17785        },
17786        end: lsp::Position {
17787            line: 0,
17788            character: 5,
17789        },
17790    };
17791
17792    let mut cx = EditorLspTestContext::new_rust(
17793        lsp::ServerCapabilities {
17794            completion_provider: Some(lsp::CompletionOptions {
17795                trigger_characters: Some(vec![".".to_string()]),
17796                resolve_provider: Some(true),
17797                ..Default::default()
17798            }),
17799            ..Default::default()
17800        },
17801        cx,
17802    )
17803    .await;
17804
17805    cx.set_state("fn main() { let a = 2ˇ; }");
17806    cx.simulate_keystroke(".");
17807
17808    let completion_data = default_data.clone();
17809    let completion_characters = default_commit_characters.clone();
17810    let completion_items = items.clone();
17811    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17812        let default_data = completion_data.clone();
17813        let default_commit_characters = completion_characters.clone();
17814        let items = completion_items.clone();
17815        async move {
17816            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17817                items,
17818                item_defaults: Some(lsp::CompletionListItemDefaults {
17819                    data: Some(default_data.clone()),
17820                    commit_characters: Some(default_commit_characters.clone()),
17821                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17822                        default_edit_range,
17823                    )),
17824                    insert_text_format: Some(default_insert_text_format),
17825                    insert_text_mode: Some(default_insert_text_mode),
17826                }),
17827                ..lsp::CompletionList::default()
17828            })))
17829        }
17830    })
17831    .next()
17832    .await;
17833
17834    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17835    cx.lsp
17836        .server
17837        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17838            let closure_resolved_items = resolved_items.clone();
17839            move |item_to_resolve, _| {
17840                let closure_resolved_items = closure_resolved_items.clone();
17841                async move {
17842                    closure_resolved_items.lock().push(item_to_resolve.clone());
17843                    Ok(item_to_resolve)
17844                }
17845            }
17846        })
17847        .detach();
17848
17849    cx.condition(|editor, _| editor.context_menu_visible())
17850        .await;
17851    cx.run_until_parked();
17852    cx.update_editor(|editor, _, _| {
17853        let menu = editor.context_menu.borrow_mut();
17854        match menu.as_ref().expect("should have the completions menu") {
17855            CodeContextMenu::Completions(completions_menu) => {
17856                assert_eq!(
17857                    completions_menu
17858                        .entries
17859                        .borrow()
17860                        .iter()
17861                        .map(|mat| mat.string.clone())
17862                        .collect::<Vec<String>>(),
17863                    items
17864                        .iter()
17865                        .map(|completion| completion.label.clone())
17866                        .collect::<Vec<String>>()
17867                );
17868            }
17869            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17870        }
17871    });
17872    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17873    // with 4 from the end.
17874    assert_eq!(
17875        *resolved_items.lock(),
17876        [&items[0..16], &items[items.len() - 4..items.len()]]
17877            .concat()
17878            .iter()
17879            .cloned()
17880            .map(|mut item| {
17881                if item.data.is_none() {
17882                    item.data = Some(default_data.clone());
17883                }
17884                item
17885            })
17886            .collect::<Vec<lsp::CompletionItem>>(),
17887        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17888    );
17889    resolved_items.lock().clear();
17890
17891    cx.update_editor(|editor, window, cx| {
17892        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17893    });
17894    cx.run_until_parked();
17895    // Completions that have already been resolved are skipped.
17896    assert_eq!(
17897        *resolved_items.lock(),
17898        items[items.len() - 17..items.len() - 4]
17899            .iter()
17900            .cloned()
17901            .map(|mut item| {
17902                if item.data.is_none() {
17903                    item.data = Some(default_data.clone());
17904                }
17905                item
17906            })
17907            .collect::<Vec<lsp::CompletionItem>>()
17908    );
17909    resolved_items.lock().clear();
17910}
17911
17912#[gpui::test]
17913async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17914    init_test(cx, |_| {});
17915
17916    let mut cx = EditorLspTestContext::new(
17917        Language::new(
17918            LanguageConfig {
17919                matcher: LanguageMatcher {
17920                    path_suffixes: vec!["jsx".into()],
17921                    ..Default::default()
17922                },
17923                overrides: [(
17924                    "element".into(),
17925                    LanguageConfigOverride {
17926                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
17927                        ..Default::default()
17928                    },
17929                )]
17930                .into_iter()
17931                .collect(),
17932                ..Default::default()
17933            },
17934            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17935        )
17936        .with_override_query("(jsx_self_closing_element) @element")
17937        .unwrap(),
17938        lsp::ServerCapabilities {
17939            completion_provider: Some(lsp::CompletionOptions {
17940                trigger_characters: Some(vec![":".to_string()]),
17941                ..Default::default()
17942            }),
17943            ..Default::default()
17944        },
17945        cx,
17946    )
17947    .await;
17948
17949    cx.lsp
17950        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17951            Ok(Some(lsp::CompletionResponse::Array(vec![
17952                lsp::CompletionItem {
17953                    label: "bg-blue".into(),
17954                    ..Default::default()
17955                },
17956                lsp::CompletionItem {
17957                    label: "bg-red".into(),
17958                    ..Default::default()
17959                },
17960                lsp::CompletionItem {
17961                    label: "bg-yellow".into(),
17962                    ..Default::default()
17963                },
17964            ])))
17965        });
17966
17967    cx.set_state(r#"<p class="bgˇ" />"#);
17968
17969    // Trigger completion when typing a dash, because the dash is an extra
17970    // word character in the 'element' scope, which contains the cursor.
17971    cx.simulate_keystroke("-");
17972    cx.executor().run_until_parked();
17973    cx.update_editor(|editor, _, _| {
17974        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17975        {
17976            assert_eq!(
17977                completion_menu_entries(menu),
17978                &["bg-blue", "bg-red", "bg-yellow"]
17979            );
17980        } else {
17981            panic!("expected completion menu to be open");
17982        }
17983    });
17984
17985    cx.simulate_keystroke("l");
17986    cx.executor().run_until_parked();
17987    cx.update_editor(|editor, _, _| {
17988        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17989        {
17990            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
17991        } else {
17992            panic!("expected completion menu to be open");
17993        }
17994    });
17995
17996    // When filtering completions, consider the character after the '-' to
17997    // be the start of a subword.
17998    cx.set_state(r#"<p class="yelˇ" />"#);
17999    cx.simulate_keystroke("l");
18000    cx.executor().run_until_parked();
18001    cx.update_editor(|editor, _, _| {
18002        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18003        {
18004            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18005        } else {
18006            panic!("expected completion menu to be open");
18007        }
18008    });
18009}
18010
18011fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18012    let entries = menu.entries.borrow();
18013    entries.iter().map(|mat| mat.string.clone()).collect()
18014}
18015
18016#[gpui::test]
18017async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18018    init_test(cx, |settings| {
18019        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
18020            Formatter::Prettier,
18021        )))
18022    });
18023
18024    let fs = FakeFs::new(cx.executor());
18025    fs.insert_file(path!("/file.ts"), Default::default()).await;
18026
18027    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18028    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18029
18030    language_registry.add(Arc::new(Language::new(
18031        LanguageConfig {
18032            name: "TypeScript".into(),
18033            matcher: LanguageMatcher {
18034                path_suffixes: vec!["ts".to_string()],
18035                ..Default::default()
18036            },
18037            ..Default::default()
18038        },
18039        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18040    )));
18041    update_test_language_settings(cx, |settings| {
18042        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18043    });
18044
18045    let test_plugin = "test_plugin";
18046    let _ = language_registry.register_fake_lsp(
18047        "TypeScript",
18048        FakeLspAdapter {
18049            prettier_plugins: vec![test_plugin],
18050            ..Default::default()
18051        },
18052    );
18053
18054    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18055    let buffer = project
18056        .update(cx, |project, cx| {
18057            project.open_local_buffer(path!("/file.ts"), cx)
18058        })
18059        .await
18060        .unwrap();
18061
18062    let buffer_text = "one\ntwo\nthree\n";
18063    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18064    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18065    editor.update_in(cx, |editor, window, cx| {
18066        editor.set_text(buffer_text, window, cx)
18067    });
18068
18069    editor
18070        .update_in(cx, |editor, window, cx| {
18071            editor.perform_format(
18072                project.clone(),
18073                FormatTrigger::Manual,
18074                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18075                window,
18076                cx,
18077            )
18078        })
18079        .unwrap()
18080        .await;
18081    assert_eq!(
18082        editor.update(cx, |editor, cx| editor.text(cx)),
18083        buffer_text.to_string() + prettier_format_suffix,
18084        "Test prettier formatting was not applied to the original buffer text",
18085    );
18086
18087    update_test_language_settings(cx, |settings| {
18088        settings.defaults.formatter = Some(SelectedFormatter::Auto)
18089    });
18090    let format = editor.update_in(cx, |editor, window, cx| {
18091        editor.perform_format(
18092            project.clone(),
18093            FormatTrigger::Manual,
18094            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18095            window,
18096            cx,
18097        )
18098    });
18099    format.await.unwrap();
18100    assert_eq!(
18101        editor.update(cx, |editor, cx| editor.text(cx)),
18102        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18103        "Autoformatting (via test prettier) was not applied to the original buffer text",
18104    );
18105}
18106
18107#[gpui::test]
18108async fn test_addition_reverts(cx: &mut TestAppContext) {
18109    init_test(cx, |_| {});
18110    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18111    let base_text = indoc! {r#"
18112        struct Row;
18113        struct Row1;
18114        struct Row2;
18115
18116        struct Row4;
18117        struct Row5;
18118        struct Row6;
18119
18120        struct Row8;
18121        struct Row9;
18122        struct Row10;"#};
18123
18124    // When addition hunks are not adjacent to carets, no hunk revert is performed
18125    assert_hunk_revert(
18126        indoc! {r#"struct Row;
18127                   struct Row1;
18128                   struct Row1.1;
18129                   struct Row1.2;
18130                   struct Row2;ˇ
18131
18132                   struct Row4;
18133                   struct Row5;
18134                   struct Row6;
18135
18136                   struct Row8;
18137                   ˇstruct Row9;
18138                   struct Row9.1;
18139                   struct Row9.2;
18140                   struct Row9.3;
18141                   struct Row10;"#},
18142        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18143        indoc! {r#"struct Row;
18144                   struct Row1;
18145                   struct Row1.1;
18146                   struct Row1.2;
18147                   struct Row2;ˇ
18148
18149                   struct Row4;
18150                   struct Row5;
18151                   struct Row6;
18152
18153                   struct Row8;
18154                   ˇstruct Row9;
18155                   struct Row9.1;
18156                   struct Row9.2;
18157                   struct Row9.3;
18158                   struct Row10;"#},
18159        base_text,
18160        &mut cx,
18161    );
18162    // Same for selections
18163    assert_hunk_revert(
18164        indoc! {r#"struct Row;
18165                   struct Row1;
18166                   struct Row2;
18167                   struct Row2.1;
18168                   struct Row2.2;
18169                   «ˇ
18170                   struct Row4;
18171                   struct» Row5;
18172                   «struct Row6;
18173                   ˇ»
18174                   struct Row9.1;
18175                   struct Row9.2;
18176                   struct Row9.3;
18177                   struct Row8;
18178                   struct Row9;
18179                   struct Row10;"#},
18180        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18181        indoc! {r#"struct Row;
18182                   struct Row1;
18183                   struct Row2;
18184                   struct Row2.1;
18185                   struct Row2.2;
18186                   «ˇ
18187                   struct Row4;
18188                   struct» Row5;
18189                   «struct Row6;
18190                   ˇ»
18191                   struct Row9.1;
18192                   struct Row9.2;
18193                   struct Row9.3;
18194                   struct Row8;
18195                   struct Row9;
18196                   struct Row10;"#},
18197        base_text,
18198        &mut cx,
18199    );
18200
18201    // When carets and selections intersect the addition hunks, those are reverted.
18202    // Adjacent carets got merged.
18203    assert_hunk_revert(
18204        indoc! {r#"struct Row;
18205                   ˇ// something on the top
18206                   struct Row1;
18207                   struct Row2;
18208                   struct Roˇw3.1;
18209                   struct Row2.2;
18210                   struct Row2.3;ˇ
18211
18212                   struct Row4;
18213                   struct ˇRow5.1;
18214                   struct Row5.2;
18215                   struct «Rowˇ»5.3;
18216                   struct Row5;
18217                   struct Row6;
18218                   ˇ
18219                   struct Row9.1;
18220                   struct «Rowˇ»9.2;
18221                   struct «ˇRow»9.3;
18222                   struct Row8;
18223                   struct Row9;
18224                   «ˇ// something on bottom»
18225                   struct Row10;"#},
18226        vec![
18227            DiffHunkStatusKind::Added,
18228            DiffHunkStatusKind::Added,
18229            DiffHunkStatusKind::Added,
18230            DiffHunkStatusKind::Added,
18231            DiffHunkStatusKind::Added,
18232        ],
18233        indoc! {r#"struct Row;
18234                   ˇstruct Row1;
18235                   struct Row2;
18236                   ˇ
18237                   struct Row4;
18238                   ˇstruct Row5;
18239                   struct Row6;
18240                   ˇ
18241                   ˇstruct Row8;
18242                   struct Row9;
18243                   ˇstruct Row10;"#},
18244        base_text,
18245        &mut cx,
18246    );
18247}
18248
18249#[gpui::test]
18250async fn test_modification_reverts(cx: &mut TestAppContext) {
18251    init_test(cx, |_| {});
18252    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18253    let base_text = indoc! {r#"
18254        struct Row;
18255        struct Row1;
18256        struct Row2;
18257
18258        struct Row4;
18259        struct Row5;
18260        struct Row6;
18261
18262        struct Row8;
18263        struct Row9;
18264        struct Row10;"#};
18265
18266    // Modification hunks behave the same as the addition ones.
18267    assert_hunk_revert(
18268        indoc! {r#"struct Row;
18269                   struct Row1;
18270                   struct Row33;
18271                   ˇ
18272                   struct Row4;
18273                   struct Row5;
18274                   struct Row6;
18275                   ˇ
18276                   struct Row99;
18277                   struct Row9;
18278                   struct Row10;"#},
18279        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18280        indoc! {r#"struct Row;
18281                   struct Row1;
18282                   struct Row33;
18283                   ˇ
18284                   struct Row4;
18285                   struct Row5;
18286                   struct Row6;
18287                   ˇ
18288                   struct Row99;
18289                   struct Row9;
18290                   struct Row10;"#},
18291        base_text,
18292        &mut cx,
18293    );
18294    assert_hunk_revert(
18295        indoc! {r#"struct Row;
18296                   struct Row1;
18297                   struct Row33;
18298                   «ˇ
18299                   struct Row4;
18300                   struct» Row5;
18301                   «struct Row6;
18302                   ˇ»
18303                   struct Row99;
18304                   struct Row9;
18305                   struct Row10;"#},
18306        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18307        indoc! {r#"struct Row;
18308                   struct Row1;
18309                   struct Row33;
18310                   «ˇ
18311                   struct Row4;
18312                   struct» Row5;
18313                   «struct Row6;
18314                   ˇ»
18315                   struct Row99;
18316                   struct Row9;
18317                   struct Row10;"#},
18318        base_text,
18319        &mut cx,
18320    );
18321
18322    assert_hunk_revert(
18323        indoc! {r#"ˇstruct Row1.1;
18324                   struct Row1;
18325                   «ˇstr»uct Row22;
18326
18327                   struct ˇRow44;
18328                   struct Row5;
18329                   struct «Rˇ»ow66;ˇ
18330
18331                   «struˇ»ct Row88;
18332                   struct Row9;
18333                   struct Row1011;ˇ"#},
18334        vec![
18335            DiffHunkStatusKind::Modified,
18336            DiffHunkStatusKind::Modified,
18337            DiffHunkStatusKind::Modified,
18338            DiffHunkStatusKind::Modified,
18339            DiffHunkStatusKind::Modified,
18340            DiffHunkStatusKind::Modified,
18341        ],
18342        indoc! {r#"struct Row;
18343                   ˇstruct Row1;
18344                   struct Row2;
18345                   ˇ
18346                   struct Row4;
18347                   ˇstruct Row5;
18348                   struct Row6;
18349                   ˇ
18350                   struct Row8;
18351                   ˇstruct Row9;
18352                   struct Row10;ˇ"#},
18353        base_text,
18354        &mut cx,
18355    );
18356}
18357
18358#[gpui::test]
18359async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18360    init_test(cx, |_| {});
18361    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18362    let base_text = indoc! {r#"
18363        one
18364
18365        two
18366        three
18367        "#};
18368
18369    cx.set_head_text(base_text);
18370    cx.set_state("\nˇ\n");
18371    cx.executor().run_until_parked();
18372    cx.update_editor(|editor, _window, cx| {
18373        editor.expand_selected_diff_hunks(cx);
18374    });
18375    cx.executor().run_until_parked();
18376    cx.update_editor(|editor, window, cx| {
18377        editor.backspace(&Default::default(), window, cx);
18378    });
18379    cx.run_until_parked();
18380    cx.assert_state_with_diff(
18381        indoc! {r#"
18382
18383        - two
18384        - threeˇ
18385        +
18386        "#}
18387        .to_string(),
18388    );
18389}
18390
18391#[gpui::test]
18392async fn test_deletion_reverts(cx: &mut TestAppContext) {
18393    init_test(cx, |_| {});
18394    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18395    let base_text = indoc! {r#"struct Row;
18396struct Row1;
18397struct Row2;
18398
18399struct Row4;
18400struct Row5;
18401struct Row6;
18402
18403struct Row8;
18404struct Row9;
18405struct Row10;"#};
18406
18407    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18408    assert_hunk_revert(
18409        indoc! {r#"struct Row;
18410                   struct Row2;
18411
18412                   ˇstruct Row4;
18413                   struct Row5;
18414                   struct Row6;
18415                   ˇ
18416                   struct Row8;
18417                   struct Row10;"#},
18418        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18419        indoc! {r#"struct Row;
18420                   struct Row2;
18421
18422                   ˇstruct Row4;
18423                   struct Row5;
18424                   struct Row6;
18425                   ˇ
18426                   struct Row8;
18427                   struct Row10;"#},
18428        base_text,
18429        &mut cx,
18430    );
18431    assert_hunk_revert(
18432        indoc! {r#"struct Row;
18433                   struct Row2;
18434
18435                   «ˇstruct Row4;
18436                   struct» Row5;
18437                   «struct Row6;
18438                   ˇ»
18439                   struct Row8;
18440                   struct Row10;"#},
18441        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18442        indoc! {r#"struct Row;
18443                   struct Row2;
18444
18445                   «ˇstruct Row4;
18446                   struct» Row5;
18447                   «struct Row6;
18448                   ˇ»
18449                   struct Row8;
18450                   struct Row10;"#},
18451        base_text,
18452        &mut cx,
18453    );
18454
18455    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18456    assert_hunk_revert(
18457        indoc! {r#"struct Row;
18458                   ˇstruct Row2;
18459
18460                   struct Row4;
18461                   struct Row5;
18462                   struct Row6;
18463
18464                   struct Row8;ˇ
18465                   struct Row10;"#},
18466        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18467        indoc! {r#"struct Row;
18468                   struct Row1;
18469                   ˇstruct Row2;
18470
18471                   struct Row4;
18472                   struct Row5;
18473                   struct Row6;
18474
18475                   struct Row8;ˇ
18476                   struct Row9;
18477                   struct Row10;"#},
18478        base_text,
18479        &mut cx,
18480    );
18481    assert_hunk_revert(
18482        indoc! {r#"struct Row;
18483                   struct Row2«ˇ;
18484                   struct Row4;
18485                   struct» Row5;
18486                   «struct Row6;
18487
18488                   struct Row8;ˇ»
18489                   struct Row10;"#},
18490        vec![
18491            DiffHunkStatusKind::Deleted,
18492            DiffHunkStatusKind::Deleted,
18493            DiffHunkStatusKind::Deleted,
18494        ],
18495        indoc! {r#"struct Row;
18496                   struct Row1;
18497                   struct Row2«ˇ;
18498
18499                   struct Row4;
18500                   struct» Row5;
18501                   «struct Row6;
18502
18503                   struct Row8;ˇ»
18504                   struct Row9;
18505                   struct Row10;"#},
18506        base_text,
18507        &mut cx,
18508    );
18509}
18510
18511#[gpui::test]
18512async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18513    init_test(cx, |_| {});
18514
18515    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18516    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18517    let base_text_3 =
18518        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18519
18520    let text_1 = edit_first_char_of_every_line(base_text_1);
18521    let text_2 = edit_first_char_of_every_line(base_text_2);
18522    let text_3 = edit_first_char_of_every_line(base_text_3);
18523
18524    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18525    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18526    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18527
18528    let multibuffer = cx.new(|cx| {
18529        let mut multibuffer = MultiBuffer::new(ReadWrite);
18530        multibuffer.push_excerpts(
18531            buffer_1.clone(),
18532            [
18533                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18534                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18535                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18536            ],
18537            cx,
18538        );
18539        multibuffer.push_excerpts(
18540            buffer_2.clone(),
18541            [
18542                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18543                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18544                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18545            ],
18546            cx,
18547        );
18548        multibuffer.push_excerpts(
18549            buffer_3.clone(),
18550            [
18551                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18552                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18553                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18554            ],
18555            cx,
18556        );
18557        multibuffer
18558    });
18559
18560    let fs = FakeFs::new(cx.executor());
18561    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18562    let (editor, cx) = cx
18563        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18564    editor.update_in(cx, |editor, _window, cx| {
18565        for (buffer, diff_base) in [
18566            (buffer_1.clone(), base_text_1),
18567            (buffer_2.clone(), base_text_2),
18568            (buffer_3.clone(), base_text_3),
18569        ] {
18570            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18571            editor
18572                .buffer
18573                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18574        }
18575    });
18576    cx.executor().run_until_parked();
18577
18578    editor.update_in(cx, |editor, window, cx| {
18579        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}");
18580        editor.select_all(&SelectAll, window, cx);
18581        editor.git_restore(&Default::default(), window, cx);
18582    });
18583    cx.executor().run_until_parked();
18584
18585    // When all ranges are selected, all buffer hunks are reverted.
18586    editor.update(cx, |editor, cx| {
18587        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");
18588    });
18589    buffer_1.update(cx, |buffer, _| {
18590        assert_eq!(buffer.text(), base_text_1);
18591    });
18592    buffer_2.update(cx, |buffer, _| {
18593        assert_eq!(buffer.text(), base_text_2);
18594    });
18595    buffer_3.update(cx, |buffer, _| {
18596        assert_eq!(buffer.text(), base_text_3);
18597    });
18598
18599    editor.update_in(cx, |editor, window, cx| {
18600        editor.undo(&Default::default(), window, cx);
18601    });
18602
18603    editor.update_in(cx, |editor, window, cx| {
18604        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18605            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18606        });
18607        editor.git_restore(&Default::default(), window, cx);
18608    });
18609
18610    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18611    // but not affect buffer_2 and its related excerpts.
18612    editor.update(cx, |editor, cx| {
18613        assert_eq!(
18614            editor.text(cx),
18615            "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}"
18616        );
18617    });
18618    buffer_1.update(cx, |buffer, _| {
18619        assert_eq!(buffer.text(), base_text_1);
18620    });
18621    buffer_2.update(cx, |buffer, _| {
18622        assert_eq!(
18623            buffer.text(),
18624            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18625        );
18626    });
18627    buffer_3.update(cx, |buffer, _| {
18628        assert_eq!(
18629            buffer.text(),
18630            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18631        );
18632    });
18633
18634    fn edit_first_char_of_every_line(text: &str) -> String {
18635        text.split('\n')
18636            .map(|line| format!("X{}", &line[1..]))
18637            .collect::<Vec<_>>()
18638            .join("\n")
18639    }
18640}
18641
18642#[gpui::test]
18643async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18644    init_test(cx, |_| {});
18645
18646    let cols = 4;
18647    let rows = 10;
18648    let sample_text_1 = sample_text(rows, cols, 'a');
18649    assert_eq!(
18650        sample_text_1,
18651        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18652    );
18653    let sample_text_2 = sample_text(rows, cols, 'l');
18654    assert_eq!(
18655        sample_text_2,
18656        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18657    );
18658    let sample_text_3 = sample_text(rows, cols, 'v');
18659    assert_eq!(
18660        sample_text_3,
18661        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18662    );
18663
18664    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18665    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18666    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18667
18668    let multi_buffer = cx.new(|cx| {
18669        let mut multibuffer = MultiBuffer::new(ReadWrite);
18670        multibuffer.push_excerpts(
18671            buffer_1.clone(),
18672            [
18673                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18674                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18675                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18676            ],
18677            cx,
18678        );
18679        multibuffer.push_excerpts(
18680            buffer_2.clone(),
18681            [
18682                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18683                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18684                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18685            ],
18686            cx,
18687        );
18688        multibuffer.push_excerpts(
18689            buffer_3.clone(),
18690            [
18691                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18692                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18693                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18694            ],
18695            cx,
18696        );
18697        multibuffer
18698    });
18699
18700    let fs = FakeFs::new(cx.executor());
18701    fs.insert_tree(
18702        "/a",
18703        json!({
18704            "main.rs": sample_text_1,
18705            "other.rs": sample_text_2,
18706            "lib.rs": sample_text_3,
18707        }),
18708    )
18709    .await;
18710    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18711    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18712    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18713    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18714        Editor::new(
18715            EditorMode::full(),
18716            multi_buffer,
18717            Some(project.clone()),
18718            window,
18719            cx,
18720        )
18721    });
18722    let multibuffer_item_id = workspace
18723        .update(cx, |workspace, window, cx| {
18724            assert!(
18725                workspace.active_item(cx).is_none(),
18726                "active item should be None before the first item is added"
18727            );
18728            workspace.add_item_to_active_pane(
18729                Box::new(multi_buffer_editor.clone()),
18730                None,
18731                true,
18732                window,
18733                cx,
18734            );
18735            let active_item = workspace
18736                .active_item(cx)
18737                .expect("should have an active item after adding the multi buffer");
18738            assert!(
18739                !active_item.is_singleton(cx),
18740                "A multi buffer was expected to active after adding"
18741            );
18742            active_item.item_id()
18743        })
18744        .unwrap();
18745    cx.executor().run_until_parked();
18746
18747    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18748        editor.change_selections(
18749            SelectionEffects::scroll(Autoscroll::Next),
18750            window,
18751            cx,
18752            |s| s.select_ranges(Some(1..2)),
18753        );
18754        editor.open_excerpts(&OpenExcerpts, window, cx);
18755    });
18756    cx.executor().run_until_parked();
18757    let first_item_id = workspace
18758        .update(cx, |workspace, window, cx| {
18759            let active_item = workspace
18760                .active_item(cx)
18761                .expect("should have an active item after navigating into the 1st buffer");
18762            let first_item_id = active_item.item_id();
18763            assert_ne!(
18764                first_item_id, multibuffer_item_id,
18765                "Should navigate into the 1st buffer and activate it"
18766            );
18767            assert!(
18768                active_item.is_singleton(cx),
18769                "New active item should be a singleton buffer"
18770            );
18771            assert_eq!(
18772                active_item
18773                    .act_as::<Editor>(cx)
18774                    .expect("should have navigated into an editor for the 1st buffer")
18775                    .read(cx)
18776                    .text(cx),
18777                sample_text_1
18778            );
18779
18780            workspace
18781                .go_back(workspace.active_pane().downgrade(), window, cx)
18782                .detach_and_log_err(cx);
18783
18784            first_item_id
18785        })
18786        .unwrap();
18787    cx.executor().run_until_parked();
18788    workspace
18789        .update(cx, |workspace, _, cx| {
18790            let active_item = workspace
18791                .active_item(cx)
18792                .expect("should have an active item after navigating back");
18793            assert_eq!(
18794                active_item.item_id(),
18795                multibuffer_item_id,
18796                "Should navigate back to the multi buffer"
18797            );
18798            assert!(!active_item.is_singleton(cx));
18799        })
18800        .unwrap();
18801
18802    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18803        editor.change_selections(
18804            SelectionEffects::scroll(Autoscroll::Next),
18805            window,
18806            cx,
18807            |s| s.select_ranges(Some(39..40)),
18808        );
18809        editor.open_excerpts(&OpenExcerpts, window, cx);
18810    });
18811    cx.executor().run_until_parked();
18812    let second_item_id = workspace
18813        .update(cx, |workspace, window, cx| {
18814            let active_item = workspace
18815                .active_item(cx)
18816                .expect("should have an active item after navigating into the 2nd buffer");
18817            let second_item_id = active_item.item_id();
18818            assert_ne!(
18819                second_item_id, multibuffer_item_id,
18820                "Should navigate away from the multibuffer"
18821            );
18822            assert_ne!(
18823                second_item_id, first_item_id,
18824                "Should navigate into the 2nd buffer and activate it"
18825            );
18826            assert!(
18827                active_item.is_singleton(cx),
18828                "New active item should be a singleton buffer"
18829            );
18830            assert_eq!(
18831                active_item
18832                    .act_as::<Editor>(cx)
18833                    .expect("should have navigated into an editor")
18834                    .read(cx)
18835                    .text(cx),
18836                sample_text_2
18837            );
18838
18839            workspace
18840                .go_back(workspace.active_pane().downgrade(), window, cx)
18841                .detach_and_log_err(cx);
18842
18843            second_item_id
18844        })
18845        .unwrap();
18846    cx.executor().run_until_parked();
18847    workspace
18848        .update(cx, |workspace, _, cx| {
18849            let active_item = workspace
18850                .active_item(cx)
18851                .expect("should have an active item after navigating back from the 2nd buffer");
18852            assert_eq!(
18853                active_item.item_id(),
18854                multibuffer_item_id,
18855                "Should navigate back from the 2nd buffer to the multi buffer"
18856            );
18857            assert!(!active_item.is_singleton(cx));
18858        })
18859        .unwrap();
18860
18861    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18862        editor.change_selections(
18863            SelectionEffects::scroll(Autoscroll::Next),
18864            window,
18865            cx,
18866            |s| s.select_ranges(Some(70..70)),
18867        );
18868        editor.open_excerpts(&OpenExcerpts, window, cx);
18869    });
18870    cx.executor().run_until_parked();
18871    workspace
18872        .update(cx, |workspace, window, cx| {
18873            let active_item = workspace
18874                .active_item(cx)
18875                .expect("should have an active item after navigating into the 3rd buffer");
18876            let third_item_id = active_item.item_id();
18877            assert_ne!(
18878                third_item_id, multibuffer_item_id,
18879                "Should navigate into the 3rd buffer and activate it"
18880            );
18881            assert_ne!(third_item_id, first_item_id);
18882            assert_ne!(third_item_id, second_item_id);
18883            assert!(
18884                active_item.is_singleton(cx),
18885                "New active item should be a singleton buffer"
18886            );
18887            assert_eq!(
18888                active_item
18889                    .act_as::<Editor>(cx)
18890                    .expect("should have navigated into an editor")
18891                    .read(cx)
18892                    .text(cx),
18893                sample_text_3
18894            );
18895
18896            workspace
18897                .go_back(workspace.active_pane().downgrade(), window, cx)
18898                .detach_and_log_err(cx);
18899        })
18900        .unwrap();
18901    cx.executor().run_until_parked();
18902    workspace
18903        .update(cx, |workspace, _, cx| {
18904            let active_item = workspace
18905                .active_item(cx)
18906                .expect("should have an active item after navigating back from the 3rd buffer");
18907            assert_eq!(
18908                active_item.item_id(),
18909                multibuffer_item_id,
18910                "Should navigate back from the 3rd buffer to the multi buffer"
18911            );
18912            assert!(!active_item.is_singleton(cx));
18913        })
18914        .unwrap();
18915}
18916
18917#[gpui::test]
18918async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18919    init_test(cx, |_| {});
18920
18921    let mut cx = EditorTestContext::new(cx).await;
18922
18923    let diff_base = r#"
18924        use some::mod;
18925
18926        const A: u32 = 42;
18927
18928        fn main() {
18929            println!("hello");
18930
18931            println!("world");
18932        }
18933        "#
18934    .unindent();
18935
18936    cx.set_state(
18937        &r#"
18938        use some::modified;
18939
18940        ˇ
18941        fn main() {
18942            println!("hello there");
18943
18944            println!("around the");
18945            println!("world");
18946        }
18947        "#
18948        .unindent(),
18949    );
18950
18951    cx.set_head_text(&diff_base);
18952    executor.run_until_parked();
18953
18954    cx.update_editor(|editor, window, cx| {
18955        editor.go_to_next_hunk(&GoToHunk, window, cx);
18956        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18957    });
18958    executor.run_until_parked();
18959    cx.assert_state_with_diff(
18960        r#"
18961          use some::modified;
18962
18963
18964          fn main() {
18965        -     println!("hello");
18966        + ˇ    println!("hello there");
18967
18968              println!("around the");
18969              println!("world");
18970          }
18971        "#
18972        .unindent(),
18973    );
18974
18975    cx.update_editor(|editor, window, cx| {
18976        for _ in 0..2 {
18977            editor.go_to_next_hunk(&GoToHunk, window, cx);
18978            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18979        }
18980    });
18981    executor.run_until_parked();
18982    cx.assert_state_with_diff(
18983        r#"
18984        - use some::mod;
18985        + ˇuse some::modified;
18986
18987
18988          fn main() {
18989        -     println!("hello");
18990        +     println!("hello there");
18991
18992        +     println!("around the");
18993              println!("world");
18994          }
18995        "#
18996        .unindent(),
18997    );
18998
18999    cx.update_editor(|editor, window, cx| {
19000        editor.go_to_next_hunk(&GoToHunk, window, cx);
19001        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19002    });
19003    executor.run_until_parked();
19004    cx.assert_state_with_diff(
19005        r#"
19006        - use some::mod;
19007        + use some::modified;
19008
19009        - const A: u32 = 42;
19010          ˇ
19011          fn main() {
19012        -     println!("hello");
19013        +     println!("hello there");
19014
19015        +     println!("around the");
19016              println!("world");
19017          }
19018        "#
19019        .unindent(),
19020    );
19021
19022    cx.update_editor(|editor, window, cx| {
19023        editor.cancel(&Cancel, window, cx);
19024    });
19025
19026    cx.assert_state_with_diff(
19027        r#"
19028          use some::modified;
19029
19030          ˇ
19031          fn main() {
19032              println!("hello there");
19033
19034              println!("around the");
19035              println!("world");
19036          }
19037        "#
19038        .unindent(),
19039    );
19040}
19041
19042#[gpui::test]
19043async fn test_diff_base_change_with_expanded_diff_hunks(
19044    executor: BackgroundExecutor,
19045    cx: &mut TestAppContext,
19046) {
19047    init_test(cx, |_| {});
19048
19049    let mut cx = EditorTestContext::new(cx).await;
19050
19051    let diff_base = r#"
19052        use some::mod1;
19053        use some::mod2;
19054
19055        const A: u32 = 42;
19056        const B: u32 = 42;
19057        const C: u32 = 42;
19058
19059        fn main() {
19060            println!("hello");
19061
19062            println!("world");
19063        }
19064        "#
19065    .unindent();
19066
19067    cx.set_state(
19068        &r#"
19069        use some::mod2;
19070
19071        const A: u32 = 42;
19072        const C: u32 = 42;
19073
19074        fn main(ˇ) {
19075            //println!("hello");
19076
19077            println!("world");
19078            //
19079            //
19080        }
19081        "#
19082        .unindent(),
19083    );
19084
19085    cx.set_head_text(&diff_base);
19086    executor.run_until_parked();
19087
19088    cx.update_editor(|editor, window, cx| {
19089        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19090    });
19091    executor.run_until_parked();
19092    cx.assert_state_with_diff(
19093        r#"
19094        - use some::mod1;
19095          use some::mod2;
19096
19097          const A: u32 = 42;
19098        - const B: u32 = 42;
19099          const C: u32 = 42;
19100
19101          fn main(ˇ) {
19102        -     println!("hello");
19103        +     //println!("hello");
19104
19105              println!("world");
19106        +     //
19107        +     //
19108          }
19109        "#
19110        .unindent(),
19111    );
19112
19113    cx.set_head_text("new diff base!");
19114    executor.run_until_parked();
19115    cx.assert_state_with_diff(
19116        r#"
19117        - new diff base!
19118        + use some::mod2;
19119        +
19120        + const A: u32 = 42;
19121        + const C: u32 = 42;
19122        +
19123        + fn main(ˇ) {
19124        +     //println!("hello");
19125        +
19126        +     println!("world");
19127        +     //
19128        +     //
19129        + }
19130        "#
19131        .unindent(),
19132    );
19133}
19134
19135#[gpui::test]
19136async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19137    init_test(cx, |_| {});
19138
19139    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19140    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19141    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19142    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19143    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19144    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19145
19146    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19147    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19148    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19149
19150    let multi_buffer = cx.new(|cx| {
19151        let mut multibuffer = MultiBuffer::new(ReadWrite);
19152        multibuffer.push_excerpts(
19153            buffer_1.clone(),
19154            [
19155                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19156                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19157                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19158            ],
19159            cx,
19160        );
19161        multibuffer.push_excerpts(
19162            buffer_2.clone(),
19163            [
19164                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19165                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19166                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19167            ],
19168            cx,
19169        );
19170        multibuffer.push_excerpts(
19171            buffer_3.clone(),
19172            [
19173                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19174                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19175                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19176            ],
19177            cx,
19178        );
19179        multibuffer
19180    });
19181
19182    let editor =
19183        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19184    editor
19185        .update(cx, |editor, _window, cx| {
19186            for (buffer, diff_base) in [
19187                (buffer_1.clone(), file_1_old),
19188                (buffer_2.clone(), file_2_old),
19189                (buffer_3.clone(), file_3_old),
19190            ] {
19191                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19192                editor
19193                    .buffer
19194                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19195            }
19196        })
19197        .unwrap();
19198
19199    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19200    cx.run_until_parked();
19201
19202    cx.assert_editor_state(
19203        &"
19204            ˇaaa
19205            ccc
19206            ddd
19207
19208            ggg
19209            hhh
19210
19211
19212            lll
19213            mmm
19214            NNN
19215
19216            qqq
19217            rrr
19218
19219            uuu
19220            111
19221            222
19222            333
19223
19224            666
19225            777
19226
19227            000
19228            !!!"
19229        .unindent(),
19230    );
19231
19232    cx.update_editor(|editor, window, cx| {
19233        editor.select_all(&SelectAll, window, cx);
19234        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19235    });
19236    cx.executor().run_until_parked();
19237
19238    cx.assert_state_with_diff(
19239        "
19240            «aaa
19241          - bbb
19242            ccc
19243            ddd
19244
19245            ggg
19246            hhh
19247
19248
19249            lll
19250            mmm
19251          - nnn
19252          + NNN
19253
19254            qqq
19255            rrr
19256
19257            uuu
19258            111
19259            222
19260            333
19261
19262          + 666
19263            777
19264
19265            000
19266            !!!ˇ»"
19267            .unindent(),
19268    );
19269}
19270
19271#[gpui::test]
19272async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19273    init_test(cx, |_| {});
19274
19275    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19276    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19277
19278    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19279    let multi_buffer = cx.new(|cx| {
19280        let mut multibuffer = MultiBuffer::new(ReadWrite);
19281        multibuffer.push_excerpts(
19282            buffer.clone(),
19283            [
19284                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19285                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19286                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19287            ],
19288            cx,
19289        );
19290        multibuffer
19291    });
19292
19293    let editor =
19294        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19295    editor
19296        .update(cx, |editor, _window, cx| {
19297            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19298            editor
19299                .buffer
19300                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19301        })
19302        .unwrap();
19303
19304    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19305    cx.run_until_parked();
19306
19307    cx.update_editor(|editor, window, cx| {
19308        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19309    });
19310    cx.executor().run_until_parked();
19311
19312    // When the start of a hunk coincides with the start of its excerpt,
19313    // the hunk is expanded. When the start of a hunk is earlier than
19314    // the start of its excerpt, the hunk is not expanded.
19315    cx.assert_state_with_diff(
19316        "
19317            ˇaaa
19318          - bbb
19319          + BBB
19320
19321          - ddd
19322          - eee
19323          + DDD
19324          + EEE
19325            fff
19326
19327            iii
19328        "
19329        .unindent(),
19330    );
19331}
19332
19333#[gpui::test]
19334async fn test_edits_around_expanded_insertion_hunks(
19335    executor: BackgroundExecutor,
19336    cx: &mut TestAppContext,
19337) {
19338    init_test(cx, |_| {});
19339
19340    let mut cx = EditorTestContext::new(cx).await;
19341
19342    let diff_base = r#"
19343        use some::mod1;
19344        use some::mod2;
19345
19346        const A: u32 = 42;
19347
19348        fn main() {
19349            println!("hello");
19350
19351            println!("world");
19352        }
19353        "#
19354    .unindent();
19355    executor.run_until_parked();
19356    cx.set_state(
19357        &r#"
19358        use some::mod1;
19359        use some::mod2;
19360
19361        const A: u32 = 42;
19362        const B: u32 = 42;
19363        const C: u32 = 42;
19364        ˇ
19365
19366        fn main() {
19367            println!("hello");
19368
19369            println!("world");
19370        }
19371        "#
19372        .unindent(),
19373    );
19374
19375    cx.set_head_text(&diff_base);
19376    executor.run_until_parked();
19377
19378    cx.update_editor(|editor, window, cx| {
19379        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19380    });
19381    executor.run_until_parked();
19382
19383    cx.assert_state_with_diff(
19384        r#"
19385        use some::mod1;
19386        use some::mod2;
19387
19388        const A: u32 = 42;
19389      + const B: u32 = 42;
19390      + const C: u32 = 42;
19391      + ˇ
19392
19393        fn main() {
19394            println!("hello");
19395
19396            println!("world");
19397        }
19398      "#
19399        .unindent(),
19400    );
19401
19402    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19403    executor.run_until_parked();
19404
19405    cx.assert_state_with_diff(
19406        r#"
19407        use some::mod1;
19408        use some::mod2;
19409
19410        const A: u32 = 42;
19411      + const B: u32 = 42;
19412      + const C: u32 = 42;
19413      + const D: u32 = 42;
19414      + ˇ
19415
19416        fn main() {
19417            println!("hello");
19418
19419            println!("world");
19420        }
19421      "#
19422        .unindent(),
19423    );
19424
19425    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19426    executor.run_until_parked();
19427
19428    cx.assert_state_with_diff(
19429        r#"
19430        use some::mod1;
19431        use some::mod2;
19432
19433        const A: u32 = 42;
19434      + const B: u32 = 42;
19435      + const C: u32 = 42;
19436      + const D: u32 = 42;
19437      + const E: u32 = 42;
19438      + ˇ
19439
19440        fn main() {
19441            println!("hello");
19442
19443            println!("world");
19444        }
19445      "#
19446        .unindent(),
19447    );
19448
19449    cx.update_editor(|editor, window, cx| {
19450        editor.delete_line(&DeleteLine, window, cx);
19451    });
19452    executor.run_until_parked();
19453
19454    cx.assert_state_with_diff(
19455        r#"
19456        use some::mod1;
19457        use some::mod2;
19458
19459        const A: u32 = 42;
19460      + const B: u32 = 42;
19461      + const C: u32 = 42;
19462      + const D: u32 = 42;
19463      + const E: u32 = 42;
19464        ˇ
19465        fn main() {
19466            println!("hello");
19467
19468            println!("world");
19469        }
19470      "#
19471        .unindent(),
19472    );
19473
19474    cx.update_editor(|editor, window, cx| {
19475        editor.move_up(&MoveUp, window, cx);
19476        editor.delete_line(&DeleteLine, window, cx);
19477        editor.move_up(&MoveUp, window, cx);
19478        editor.delete_line(&DeleteLine, window, cx);
19479        editor.move_up(&MoveUp, window, cx);
19480        editor.delete_line(&DeleteLine, window, cx);
19481    });
19482    executor.run_until_parked();
19483    cx.assert_state_with_diff(
19484        r#"
19485        use some::mod1;
19486        use some::mod2;
19487
19488        const A: u32 = 42;
19489      + const B: u32 = 42;
19490        ˇ
19491        fn main() {
19492            println!("hello");
19493
19494            println!("world");
19495        }
19496      "#
19497        .unindent(),
19498    );
19499
19500    cx.update_editor(|editor, window, cx| {
19501        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19502        editor.delete_line(&DeleteLine, window, cx);
19503    });
19504    executor.run_until_parked();
19505    cx.assert_state_with_diff(
19506        r#"
19507        ˇ
19508        fn main() {
19509            println!("hello");
19510
19511            println!("world");
19512        }
19513      "#
19514        .unindent(),
19515    );
19516}
19517
19518#[gpui::test]
19519async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19520    init_test(cx, |_| {});
19521
19522    let mut cx = EditorTestContext::new(cx).await;
19523    cx.set_head_text(indoc! { "
19524        one
19525        two
19526        three
19527        four
19528        five
19529        "
19530    });
19531    cx.set_state(indoc! { "
19532        one
19533        ˇthree
19534        five
19535    "});
19536    cx.run_until_parked();
19537    cx.update_editor(|editor, window, cx| {
19538        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19539    });
19540    cx.assert_state_with_diff(
19541        indoc! { "
19542        one
19543      - two
19544        ˇthree
19545      - four
19546        five
19547    "}
19548        .to_string(),
19549    );
19550    cx.update_editor(|editor, window, cx| {
19551        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19552    });
19553
19554    cx.assert_state_with_diff(
19555        indoc! { "
19556        one
19557        ˇthree
19558        five
19559    "}
19560        .to_string(),
19561    );
19562
19563    cx.set_state(indoc! { "
19564        one
19565        ˇTWO
19566        three
19567        four
19568        five
19569    "});
19570    cx.run_until_parked();
19571    cx.update_editor(|editor, window, cx| {
19572        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19573    });
19574
19575    cx.assert_state_with_diff(
19576        indoc! { "
19577            one
19578          - two
19579          + ˇTWO
19580            three
19581            four
19582            five
19583        "}
19584        .to_string(),
19585    );
19586    cx.update_editor(|editor, window, cx| {
19587        editor.move_up(&Default::default(), window, cx);
19588        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19589    });
19590    cx.assert_state_with_diff(
19591        indoc! { "
19592            one
19593            ˇTWO
19594            three
19595            four
19596            five
19597        "}
19598        .to_string(),
19599    );
19600}
19601
19602#[gpui::test]
19603async fn test_edits_around_expanded_deletion_hunks(
19604    executor: BackgroundExecutor,
19605    cx: &mut TestAppContext,
19606) {
19607    init_test(cx, |_| {});
19608
19609    let mut cx = EditorTestContext::new(cx).await;
19610
19611    let diff_base = r#"
19612        use some::mod1;
19613        use some::mod2;
19614
19615        const A: u32 = 42;
19616        const B: u32 = 42;
19617        const C: u32 = 42;
19618
19619
19620        fn main() {
19621            println!("hello");
19622
19623            println!("world");
19624        }
19625    "#
19626    .unindent();
19627    executor.run_until_parked();
19628    cx.set_state(
19629        &r#"
19630        use some::mod1;
19631        use some::mod2;
19632
19633        ˇconst B: u32 = 42;
19634        const C: u32 = 42;
19635
19636
19637        fn main() {
19638            println!("hello");
19639
19640            println!("world");
19641        }
19642        "#
19643        .unindent(),
19644    );
19645
19646    cx.set_head_text(&diff_base);
19647    executor.run_until_parked();
19648
19649    cx.update_editor(|editor, window, cx| {
19650        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19651    });
19652    executor.run_until_parked();
19653
19654    cx.assert_state_with_diff(
19655        r#"
19656        use some::mod1;
19657        use some::mod2;
19658
19659      - const A: u32 = 42;
19660        ˇconst B: u32 = 42;
19661        const C: u32 = 42;
19662
19663
19664        fn main() {
19665            println!("hello");
19666
19667            println!("world");
19668        }
19669      "#
19670        .unindent(),
19671    );
19672
19673    cx.update_editor(|editor, window, cx| {
19674        editor.delete_line(&DeleteLine, window, cx);
19675    });
19676    executor.run_until_parked();
19677    cx.assert_state_with_diff(
19678        r#"
19679        use some::mod1;
19680        use some::mod2;
19681
19682      - const A: u32 = 42;
19683      - const B: u32 = 42;
19684        ˇconst C: u32 = 42;
19685
19686
19687        fn main() {
19688            println!("hello");
19689
19690            println!("world");
19691        }
19692      "#
19693        .unindent(),
19694    );
19695
19696    cx.update_editor(|editor, window, cx| {
19697        editor.delete_line(&DeleteLine, window, cx);
19698    });
19699    executor.run_until_parked();
19700    cx.assert_state_with_diff(
19701        r#"
19702        use some::mod1;
19703        use some::mod2;
19704
19705      - const A: u32 = 42;
19706      - const B: u32 = 42;
19707      - const C: u32 = 42;
19708        ˇ
19709
19710        fn main() {
19711            println!("hello");
19712
19713            println!("world");
19714        }
19715      "#
19716        .unindent(),
19717    );
19718
19719    cx.update_editor(|editor, window, cx| {
19720        editor.handle_input("replacement", window, cx);
19721    });
19722    executor.run_until_parked();
19723    cx.assert_state_with_diff(
19724        r#"
19725        use some::mod1;
19726        use some::mod2;
19727
19728      - const A: u32 = 42;
19729      - const B: u32 = 42;
19730      - const C: u32 = 42;
19731      -
19732      + replacementˇ
19733
19734        fn main() {
19735            println!("hello");
19736
19737            println!("world");
19738        }
19739      "#
19740        .unindent(),
19741    );
19742}
19743
19744#[gpui::test]
19745async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19746    init_test(cx, |_| {});
19747
19748    let mut cx = EditorTestContext::new(cx).await;
19749
19750    let base_text = r#"
19751        one
19752        two
19753        three
19754        four
19755        five
19756    "#
19757    .unindent();
19758    executor.run_until_parked();
19759    cx.set_state(
19760        &r#"
19761        one
19762        two
19763        fˇour
19764        five
19765        "#
19766        .unindent(),
19767    );
19768
19769    cx.set_head_text(&base_text);
19770    executor.run_until_parked();
19771
19772    cx.update_editor(|editor, window, cx| {
19773        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19774    });
19775    executor.run_until_parked();
19776
19777    cx.assert_state_with_diff(
19778        r#"
19779          one
19780          two
19781        - three
19782          fˇour
19783          five
19784        "#
19785        .unindent(),
19786    );
19787
19788    cx.update_editor(|editor, window, cx| {
19789        editor.backspace(&Backspace, window, cx);
19790        editor.backspace(&Backspace, window, cx);
19791    });
19792    executor.run_until_parked();
19793    cx.assert_state_with_diff(
19794        r#"
19795          one
19796          two
19797        - threeˇ
19798        - four
19799        + our
19800          five
19801        "#
19802        .unindent(),
19803    );
19804}
19805
19806#[gpui::test]
19807async fn test_edit_after_expanded_modification_hunk(
19808    executor: BackgroundExecutor,
19809    cx: &mut TestAppContext,
19810) {
19811    init_test(cx, |_| {});
19812
19813    let mut cx = EditorTestContext::new(cx).await;
19814
19815    let diff_base = r#"
19816        use some::mod1;
19817        use some::mod2;
19818
19819        const A: u32 = 42;
19820        const B: u32 = 42;
19821        const C: u32 = 42;
19822        const D: u32 = 42;
19823
19824
19825        fn main() {
19826            println!("hello");
19827
19828            println!("world");
19829        }"#
19830    .unindent();
19831
19832    cx.set_state(
19833        &r#"
19834        use some::mod1;
19835        use some::mod2;
19836
19837        const A: u32 = 42;
19838        const B: u32 = 42;
19839        const C: u32 = 43ˇ
19840        const D: u32 = 42;
19841
19842
19843        fn main() {
19844            println!("hello");
19845
19846            println!("world");
19847        }"#
19848        .unindent(),
19849    );
19850
19851    cx.set_head_text(&diff_base);
19852    executor.run_until_parked();
19853    cx.update_editor(|editor, window, cx| {
19854        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19855    });
19856    executor.run_until_parked();
19857
19858    cx.assert_state_with_diff(
19859        r#"
19860        use some::mod1;
19861        use some::mod2;
19862
19863        const A: u32 = 42;
19864        const B: u32 = 42;
19865      - const C: u32 = 42;
19866      + const C: u32 = 43ˇ
19867        const D: u32 = 42;
19868
19869
19870        fn main() {
19871            println!("hello");
19872
19873            println!("world");
19874        }"#
19875        .unindent(),
19876    );
19877
19878    cx.update_editor(|editor, window, cx| {
19879        editor.handle_input("\nnew_line\n", window, cx);
19880    });
19881    executor.run_until_parked();
19882
19883    cx.assert_state_with_diff(
19884        r#"
19885        use some::mod1;
19886        use some::mod2;
19887
19888        const A: u32 = 42;
19889        const B: u32 = 42;
19890      - const C: u32 = 42;
19891      + const C: u32 = 43
19892      + new_line
19893      + ˇ
19894        const D: u32 = 42;
19895
19896
19897        fn main() {
19898            println!("hello");
19899
19900            println!("world");
19901        }"#
19902        .unindent(),
19903    );
19904}
19905
19906#[gpui::test]
19907async fn test_stage_and_unstage_added_file_hunk(
19908    executor: BackgroundExecutor,
19909    cx: &mut TestAppContext,
19910) {
19911    init_test(cx, |_| {});
19912
19913    let mut cx = EditorTestContext::new(cx).await;
19914    cx.update_editor(|editor, _, cx| {
19915        editor.set_expand_all_diff_hunks(cx);
19916    });
19917
19918    let working_copy = r#"
19919            ˇfn main() {
19920                println!("hello, world!");
19921            }
19922        "#
19923    .unindent();
19924
19925    cx.set_state(&working_copy);
19926    executor.run_until_parked();
19927
19928    cx.assert_state_with_diff(
19929        r#"
19930            + ˇfn main() {
19931            +     println!("hello, world!");
19932            + }
19933        "#
19934        .unindent(),
19935    );
19936    cx.assert_index_text(None);
19937
19938    cx.update_editor(|editor, window, cx| {
19939        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19940    });
19941    executor.run_until_parked();
19942    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
19943    cx.assert_state_with_diff(
19944        r#"
19945            + ˇfn main() {
19946            +     println!("hello, world!");
19947            + }
19948        "#
19949        .unindent(),
19950    );
19951
19952    cx.update_editor(|editor, window, cx| {
19953        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19954    });
19955    executor.run_until_parked();
19956    cx.assert_index_text(None);
19957}
19958
19959async fn setup_indent_guides_editor(
19960    text: &str,
19961    cx: &mut TestAppContext,
19962) -> (BufferId, EditorTestContext) {
19963    init_test(cx, |_| {});
19964
19965    let mut cx = EditorTestContext::new(cx).await;
19966
19967    let buffer_id = cx.update_editor(|editor, window, cx| {
19968        editor.set_text(text, window, cx);
19969        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
19970
19971        buffer_ids[0]
19972    });
19973
19974    (buffer_id, cx)
19975}
19976
19977fn assert_indent_guides(
19978    range: Range<u32>,
19979    expected: Vec<IndentGuide>,
19980    active_indices: Option<Vec<usize>>,
19981    cx: &mut EditorTestContext,
19982) {
19983    let indent_guides = cx.update_editor(|editor, window, cx| {
19984        let snapshot = editor.snapshot(window, cx).display_snapshot;
19985        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
19986            editor,
19987            MultiBufferRow(range.start)..MultiBufferRow(range.end),
19988            true,
19989            &snapshot,
19990            cx,
19991        );
19992
19993        indent_guides.sort_by(|a, b| {
19994            a.depth.cmp(&b.depth).then(
19995                a.start_row
19996                    .cmp(&b.start_row)
19997                    .then(a.end_row.cmp(&b.end_row)),
19998            )
19999        });
20000        indent_guides
20001    });
20002
20003    if let Some(expected) = active_indices {
20004        let active_indices = cx.update_editor(|editor, window, cx| {
20005            let snapshot = editor.snapshot(window, cx).display_snapshot;
20006            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20007        });
20008
20009        assert_eq!(
20010            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20011            expected,
20012            "Active indent guide indices do not match"
20013        );
20014    }
20015
20016    assert_eq!(indent_guides, expected, "Indent guides do not match");
20017}
20018
20019fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20020    IndentGuide {
20021        buffer_id,
20022        start_row: MultiBufferRow(start_row),
20023        end_row: MultiBufferRow(end_row),
20024        depth,
20025        tab_size: 4,
20026        settings: IndentGuideSettings {
20027            enabled: true,
20028            line_width: 1,
20029            active_line_width: 1,
20030            coloring: IndentGuideColoring::default(),
20031            background_coloring: IndentGuideBackgroundColoring::default(),
20032        },
20033    }
20034}
20035
20036#[gpui::test]
20037async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20038    let (buffer_id, mut cx) = setup_indent_guides_editor(
20039        &"
20040        fn main() {
20041            let a = 1;
20042        }"
20043        .unindent(),
20044        cx,
20045    )
20046    .await;
20047
20048    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20049}
20050
20051#[gpui::test]
20052async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20053    let (buffer_id, mut cx) = setup_indent_guides_editor(
20054        &"
20055        fn main() {
20056            let a = 1;
20057            let b = 2;
20058        }"
20059        .unindent(),
20060        cx,
20061    )
20062    .await;
20063
20064    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20065}
20066
20067#[gpui::test]
20068async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20069    let (buffer_id, mut cx) = setup_indent_guides_editor(
20070        &"
20071        fn main() {
20072            let a = 1;
20073            if a == 3 {
20074                let b = 2;
20075            } else {
20076                let c = 3;
20077            }
20078        }"
20079        .unindent(),
20080        cx,
20081    )
20082    .await;
20083
20084    assert_indent_guides(
20085        0..8,
20086        vec![
20087            indent_guide(buffer_id, 1, 6, 0),
20088            indent_guide(buffer_id, 3, 3, 1),
20089            indent_guide(buffer_id, 5, 5, 1),
20090        ],
20091        None,
20092        &mut cx,
20093    );
20094}
20095
20096#[gpui::test]
20097async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20098    let (buffer_id, mut cx) = setup_indent_guides_editor(
20099        &"
20100        fn main() {
20101            let a = 1;
20102                let b = 2;
20103            let c = 3;
20104        }"
20105        .unindent(),
20106        cx,
20107    )
20108    .await;
20109
20110    assert_indent_guides(
20111        0..5,
20112        vec![
20113            indent_guide(buffer_id, 1, 3, 0),
20114            indent_guide(buffer_id, 2, 2, 1),
20115        ],
20116        None,
20117        &mut cx,
20118    );
20119}
20120
20121#[gpui::test]
20122async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20123    let (buffer_id, mut cx) = setup_indent_guides_editor(
20124        &"
20125        fn main() {
20126            let a = 1;
20127
20128            let c = 3;
20129        }"
20130        .unindent(),
20131        cx,
20132    )
20133    .await;
20134
20135    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20136}
20137
20138#[gpui::test]
20139async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20140    let (buffer_id, mut cx) = setup_indent_guides_editor(
20141        &"
20142        fn main() {
20143            let a = 1;
20144
20145            let c = 3;
20146
20147            if a == 3 {
20148                let b = 2;
20149            } else {
20150                let c = 3;
20151            }
20152        }"
20153        .unindent(),
20154        cx,
20155    )
20156    .await;
20157
20158    assert_indent_guides(
20159        0..11,
20160        vec![
20161            indent_guide(buffer_id, 1, 9, 0),
20162            indent_guide(buffer_id, 6, 6, 1),
20163            indent_guide(buffer_id, 8, 8, 1),
20164        ],
20165        None,
20166        &mut cx,
20167    );
20168}
20169
20170#[gpui::test]
20171async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20172    let (buffer_id, mut cx) = setup_indent_guides_editor(
20173        &"
20174        fn main() {
20175            let a = 1;
20176
20177            let c = 3;
20178
20179            if a == 3 {
20180                let b = 2;
20181            } else {
20182                let c = 3;
20183            }
20184        }"
20185        .unindent(),
20186        cx,
20187    )
20188    .await;
20189
20190    assert_indent_guides(
20191        1..11,
20192        vec![
20193            indent_guide(buffer_id, 1, 9, 0),
20194            indent_guide(buffer_id, 6, 6, 1),
20195            indent_guide(buffer_id, 8, 8, 1),
20196        ],
20197        None,
20198        &mut cx,
20199    );
20200}
20201
20202#[gpui::test]
20203async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20204    let (buffer_id, mut cx) = setup_indent_guides_editor(
20205        &"
20206        fn main() {
20207            let a = 1;
20208
20209            let c = 3;
20210
20211            if a == 3 {
20212                let b = 2;
20213            } else {
20214                let c = 3;
20215            }
20216        }"
20217        .unindent(),
20218        cx,
20219    )
20220    .await;
20221
20222    assert_indent_guides(
20223        1..10,
20224        vec![
20225            indent_guide(buffer_id, 1, 9, 0),
20226            indent_guide(buffer_id, 6, 6, 1),
20227            indent_guide(buffer_id, 8, 8, 1),
20228        ],
20229        None,
20230        &mut cx,
20231    );
20232}
20233
20234#[gpui::test]
20235async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20236    let (buffer_id, mut cx) = setup_indent_guides_editor(
20237        &"
20238        fn main() {
20239            if a {
20240                b(
20241                    c,
20242                    d,
20243                )
20244            } else {
20245                e(
20246                    f
20247                )
20248            }
20249        }"
20250        .unindent(),
20251        cx,
20252    )
20253    .await;
20254
20255    assert_indent_guides(
20256        0..11,
20257        vec![
20258            indent_guide(buffer_id, 1, 10, 0),
20259            indent_guide(buffer_id, 2, 5, 1),
20260            indent_guide(buffer_id, 7, 9, 1),
20261            indent_guide(buffer_id, 3, 4, 2),
20262            indent_guide(buffer_id, 8, 8, 2),
20263        ],
20264        None,
20265        &mut cx,
20266    );
20267
20268    cx.update_editor(|editor, window, cx| {
20269        editor.fold_at(MultiBufferRow(2), window, cx);
20270        assert_eq!(
20271            editor.display_text(cx),
20272            "
20273            fn main() {
20274                if a {
20275                    b(⋯
20276                    )
20277                } else {
20278                    e(
20279                        f
20280                    )
20281                }
20282            }"
20283            .unindent()
20284        );
20285    });
20286
20287    assert_indent_guides(
20288        0..11,
20289        vec![
20290            indent_guide(buffer_id, 1, 10, 0),
20291            indent_guide(buffer_id, 2, 5, 1),
20292            indent_guide(buffer_id, 7, 9, 1),
20293            indent_guide(buffer_id, 8, 8, 2),
20294        ],
20295        None,
20296        &mut cx,
20297    );
20298}
20299
20300#[gpui::test]
20301async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20302    let (buffer_id, mut cx) = setup_indent_guides_editor(
20303        &"
20304        block1
20305            block2
20306                block3
20307                    block4
20308            block2
20309        block1
20310        block1"
20311            .unindent(),
20312        cx,
20313    )
20314    .await;
20315
20316    assert_indent_guides(
20317        1..10,
20318        vec![
20319            indent_guide(buffer_id, 1, 4, 0),
20320            indent_guide(buffer_id, 2, 3, 1),
20321            indent_guide(buffer_id, 3, 3, 2),
20322        ],
20323        None,
20324        &mut cx,
20325    );
20326}
20327
20328#[gpui::test]
20329async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20330    let (buffer_id, mut cx) = setup_indent_guides_editor(
20331        &"
20332        block1
20333            block2
20334                block3
20335
20336        block1
20337        block1"
20338            .unindent(),
20339        cx,
20340    )
20341    .await;
20342
20343    assert_indent_guides(
20344        0..6,
20345        vec![
20346            indent_guide(buffer_id, 1, 2, 0),
20347            indent_guide(buffer_id, 2, 2, 1),
20348        ],
20349        None,
20350        &mut cx,
20351    );
20352}
20353
20354#[gpui::test]
20355async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20356    let (buffer_id, mut cx) = setup_indent_guides_editor(
20357        &"
20358        function component() {
20359        \treturn (
20360        \t\t\t
20361        \t\t<div>
20362        \t\t\t<abc></abc>
20363        \t\t</div>
20364        \t)
20365        }"
20366        .unindent(),
20367        cx,
20368    )
20369    .await;
20370
20371    assert_indent_guides(
20372        0..8,
20373        vec![
20374            indent_guide(buffer_id, 1, 6, 0),
20375            indent_guide(buffer_id, 2, 5, 1),
20376            indent_guide(buffer_id, 4, 4, 2),
20377        ],
20378        None,
20379        &mut cx,
20380    );
20381}
20382
20383#[gpui::test]
20384async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20385    let (buffer_id, mut cx) = setup_indent_guides_editor(
20386        &"
20387        function component() {
20388        \treturn (
20389        \t
20390        \t\t<div>
20391        \t\t\t<abc></abc>
20392        \t\t</div>
20393        \t)
20394        }"
20395        .unindent(),
20396        cx,
20397    )
20398    .await;
20399
20400    assert_indent_guides(
20401        0..8,
20402        vec![
20403            indent_guide(buffer_id, 1, 6, 0),
20404            indent_guide(buffer_id, 2, 5, 1),
20405            indent_guide(buffer_id, 4, 4, 2),
20406        ],
20407        None,
20408        &mut cx,
20409    );
20410}
20411
20412#[gpui::test]
20413async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20414    let (buffer_id, mut cx) = setup_indent_guides_editor(
20415        &"
20416        block1
20417
20418
20419
20420            block2
20421        "
20422        .unindent(),
20423        cx,
20424    )
20425    .await;
20426
20427    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20428}
20429
20430#[gpui::test]
20431async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20432    let (buffer_id, mut cx) = setup_indent_guides_editor(
20433        &"
20434        def a:
20435        \tb = 3
20436        \tif True:
20437        \t\tc = 4
20438        \t\td = 5
20439        \tprint(b)
20440        "
20441        .unindent(),
20442        cx,
20443    )
20444    .await;
20445
20446    assert_indent_guides(
20447        0..6,
20448        vec![
20449            indent_guide(buffer_id, 1, 5, 0),
20450            indent_guide(buffer_id, 3, 4, 1),
20451        ],
20452        None,
20453        &mut cx,
20454    );
20455}
20456
20457#[gpui::test]
20458async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20459    let (buffer_id, mut cx) = setup_indent_guides_editor(
20460        &"
20461    fn main() {
20462        let a = 1;
20463    }"
20464        .unindent(),
20465        cx,
20466    )
20467    .await;
20468
20469    cx.update_editor(|editor, window, cx| {
20470        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20471            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20472        });
20473    });
20474
20475    assert_indent_guides(
20476        0..3,
20477        vec![indent_guide(buffer_id, 1, 1, 0)],
20478        Some(vec![0]),
20479        &mut cx,
20480    );
20481}
20482
20483#[gpui::test]
20484async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20485    let (buffer_id, mut cx) = setup_indent_guides_editor(
20486        &"
20487    fn main() {
20488        if 1 == 2 {
20489            let a = 1;
20490        }
20491    }"
20492        .unindent(),
20493        cx,
20494    )
20495    .await;
20496
20497    cx.update_editor(|editor, window, cx| {
20498        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20499            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20500        });
20501    });
20502
20503    assert_indent_guides(
20504        0..4,
20505        vec![
20506            indent_guide(buffer_id, 1, 3, 0),
20507            indent_guide(buffer_id, 2, 2, 1),
20508        ],
20509        Some(vec![1]),
20510        &mut cx,
20511    );
20512
20513    cx.update_editor(|editor, window, cx| {
20514        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20515            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20516        });
20517    });
20518
20519    assert_indent_guides(
20520        0..4,
20521        vec![
20522            indent_guide(buffer_id, 1, 3, 0),
20523            indent_guide(buffer_id, 2, 2, 1),
20524        ],
20525        Some(vec![1]),
20526        &mut cx,
20527    );
20528
20529    cx.update_editor(|editor, window, cx| {
20530        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20531            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20532        });
20533    });
20534
20535    assert_indent_guides(
20536        0..4,
20537        vec![
20538            indent_guide(buffer_id, 1, 3, 0),
20539            indent_guide(buffer_id, 2, 2, 1),
20540        ],
20541        Some(vec![0]),
20542        &mut cx,
20543    );
20544}
20545
20546#[gpui::test]
20547async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20548    let (buffer_id, mut cx) = setup_indent_guides_editor(
20549        &"
20550    fn main() {
20551        let a = 1;
20552
20553        let b = 2;
20554    }"
20555        .unindent(),
20556        cx,
20557    )
20558    .await;
20559
20560    cx.update_editor(|editor, window, cx| {
20561        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20562            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20563        });
20564    });
20565
20566    assert_indent_guides(
20567        0..5,
20568        vec![indent_guide(buffer_id, 1, 3, 0)],
20569        Some(vec![0]),
20570        &mut cx,
20571    );
20572}
20573
20574#[gpui::test]
20575async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20576    let (buffer_id, mut cx) = setup_indent_guides_editor(
20577        &"
20578    def m:
20579        a = 1
20580        pass"
20581            .unindent(),
20582        cx,
20583    )
20584    .await;
20585
20586    cx.update_editor(|editor, window, cx| {
20587        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20588            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20589        });
20590    });
20591
20592    assert_indent_guides(
20593        0..3,
20594        vec![indent_guide(buffer_id, 1, 2, 0)],
20595        Some(vec![0]),
20596        &mut cx,
20597    );
20598}
20599
20600#[gpui::test]
20601async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20602    init_test(cx, |_| {});
20603    let mut cx = EditorTestContext::new(cx).await;
20604    let text = indoc! {
20605        "
20606        impl A {
20607            fn b() {
20608                0;
20609                3;
20610                5;
20611                6;
20612                7;
20613            }
20614        }
20615        "
20616    };
20617    let base_text = indoc! {
20618        "
20619        impl A {
20620            fn b() {
20621                0;
20622                1;
20623                2;
20624                3;
20625                4;
20626            }
20627            fn c() {
20628                5;
20629                6;
20630                7;
20631            }
20632        }
20633        "
20634    };
20635
20636    cx.update_editor(|editor, window, cx| {
20637        editor.set_text(text, window, cx);
20638
20639        editor.buffer().update(cx, |multibuffer, cx| {
20640            let buffer = multibuffer.as_singleton().unwrap();
20641            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20642
20643            multibuffer.set_all_diff_hunks_expanded(cx);
20644            multibuffer.add_diff(diff, cx);
20645
20646            buffer.read(cx).remote_id()
20647        })
20648    });
20649    cx.run_until_parked();
20650
20651    cx.assert_state_with_diff(
20652        indoc! { "
20653          impl A {
20654              fn b() {
20655                  0;
20656        -         1;
20657        -         2;
20658                  3;
20659        -         4;
20660        -     }
20661        -     fn c() {
20662                  5;
20663                  6;
20664                  7;
20665              }
20666          }
20667          ˇ"
20668        }
20669        .to_string(),
20670    );
20671
20672    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20673        editor
20674            .snapshot(window, cx)
20675            .buffer_snapshot
20676            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20677            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20678            .collect::<Vec<_>>()
20679    });
20680    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20681    assert_eq!(
20682        actual_guides,
20683        vec![
20684            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20685            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20686            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20687        ]
20688    );
20689}
20690
20691#[gpui::test]
20692async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20693    init_test(cx, |_| {});
20694    let mut cx = EditorTestContext::new(cx).await;
20695
20696    let diff_base = r#"
20697        a
20698        b
20699        c
20700        "#
20701    .unindent();
20702
20703    cx.set_state(
20704        &r#"
20705        ˇA
20706        b
20707        C
20708        "#
20709        .unindent(),
20710    );
20711    cx.set_head_text(&diff_base);
20712    cx.update_editor(|editor, window, cx| {
20713        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20714    });
20715    executor.run_until_parked();
20716
20717    let both_hunks_expanded = r#"
20718        - a
20719        + ˇA
20720          b
20721        - c
20722        + C
20723        "#
20724    .unindent();
20725
20726    cx.assert_state_with_diff(both_hunks_expanded.clone());
20727
20728    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20729        let snapshot = editor.snapshot(window, cx);
20730        let hunks = editor
20731            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20732            .collect::<Vec<_>>();
20733        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20734        let buffer_id = hunks[0].buffer_id;
20735        hunks
20736            .into_iter()
20737            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20738            .collect::<Vec<_>>()
20739    });
20740    assert_eq!(hunk_ranges.len(), 2);
20741
20742    cx.update_editor(|editor, _, cx| {
20743        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20744    });
20745    executor.run_until_parked();
20746
20747    let second_hunk_expanded = r#"
20748          ˇA
20749          b
20750        - c
20751        + C
20752        "#
20753    .unindent();
20754
20755    cx.assert_state_with_diff(second_hunk_expanded);
20756
20757    cx.update_editor(|editor, _, cx| {
20758        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20759    });
20760    executor.run_until_parked();
20761
20762    cx.assert_state_with_diff(both_hunks_expanded.clone());
20763
20764    cx.update_editor(|editor, _, cx| {
20765        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20766    });
20767    executor.run_until_parked();
20768
20769    let first_hunk_expanded = r#"
20770        - a
20771        + ˇA
20772          b
20773          C
20774        "#
20775    .unindent();
20776
20777    cx.assert_state_with_diff(first_hunk_expanded);
20778
20779    cx.update_editor(|editor, _, cx| {
20780        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20781    });
20782    executor.run_until_parked();
20783
20784    cx.assert_state_with_diff(both_hunks_expanded);
20785
20786    cx.set_state(
20787        &r#"
20788        ˇA
20789        b
20790        "#
20791        .unindent(),
20792    );
20793    cx.run_until_parked();
20794
20795    // TODO this cursor position seems bad
20796    cx.assert_state_with_diff(
20797        r#"
20798        - ˇa
20799        + A
20800          b
20801        "#
20802        .unindent(),
20803    );
20804
20805    cx.update_editor(|editor, window, cx| {
20806        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20807    });
20808
20809    cx.assert_state_with_diff(
20810        r#"
20811            - ˇa
20812            + A
20813              b
20814            - c
20815            "#
20816        .unindent(),
20817    );
20818
20819    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20820        let snapshot = editor.snapshot(window, cx);
20821        let hunks = editor
20822            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20823            .collect::<Vec<_>>();
20824        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20825        let buffer_id = hunks[0].buffer_id;
20826        hunks
20827            .into_iter()
20828            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20829            .collect::<Vec<_>>()
20830    });
20831    assert_eq!(hunk_ranges.len(), 2);
20832
20833    cx.update_editor(|editor, _, cx| {
20834        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20835    });
20836    executor.run_until_parked();
20837
20838    cx.assert_state_with_diff(
20839        r#"
20840        - ˇa
20841        + A
20842          b
20843        "#
20844        .unindent(),
20845    );
20846}
20847
20848#[gpui::test]
20849async fn test_toggle_deletion_hunk_at_start_of_file(
20850    executor: BackgroundExecutor,
20851    cx: &mut TestAppContext,
20852) {
20853    init_test(cx, |_| {});
20854    let mut cx = EditorTestContext::new(cx).await;
20855
20856    let diff_base = r#"
20857        a
20858        b
20859        c
20860        "#
20861    .unindent();
20862
20863    cx.set_state(
20864        &r#"
20865        ˇb
20866        c
20867        "#
20868        .unindent(),
20869    );
20870    cx.set_head_text(&diff_base);
20871    cx.update_editor(|editor, window, cx| {
20872        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20873    });
20874    executor.run_until_parked();
20875
20876    let hunk_expanded = r#"
20877        - a
20878          ˇb
20879          c
20880        "#
20881    .unindent();
20882
20883    cx.assert_state_with_diff(hunk_expanded.clone());
20884
20885    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20886        let snapshot = editor.snapshot(window, cx);
20887        let hunks = editor
20888            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20889            .collect::<Vec<_>>();
20890        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20891        let buffer_id = hunks[0].buffer_id;
20892        hunks
20893            .into_iter()
20894            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20895            .collect::<Vec<_>>()
20896    });
20897    assert_eq!(hunk_ranges.len(), 1);
20898
20899    cx.update_editor(|editor, _, cx| {
20900        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20901    });
20902    executor.run_until_parked();
20903
20904    let hunk_collapsed = r#"
20905          ˇb
20906          c
20907        "#
20908    .unindent();
20909
20910    cx.assert_state_with_diff(hunk_collapsed);
20911
20912    cx.update_editor(|editor, _, cx| {
20913        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20914    });
20915    executor.run_until_parked();
20916
20917    cx.assert_state_with_diff(hunk_expanded);
20918}
20919
20920#[gpui::test]
20921async fn test_display_diff_hunks(cx: &mut TestAppContext) {
20922    init_test(cx, |_| {});
20923
20924    let fs = FakeFs::new(cx.executor());
20925    fs.insert_tree(
20926        path!("/test"),
20927        json!({
20928            ".git": {},
20929            "file-1": "ONE\n",
20930            "file-2": "TWO\n",
20931            "file-3": "THREE\n",
20932        }),
20933    )
20934    .await;
20935
20936    fs.set_head_for_repo(
20937        path!("/test/.git").as_ref(),
20938        &[
20939            ("file-1".into(), "one\n".into()),
20940            ("file-2".into(), "two\n".into()),
20941            ("file-3".into(), "three\n".into()),
20942        ],
20943        "deadbeef",
20944    );
20945
20946    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
20947    let mut buffers = vec![];
20948    for i in 1..=3 {
20949        let buffer = project
20950            .update(cx, |project, cx| {
20951                let path = format!(path!("/test/file-{}"), i);
20952                project.open_local_buffer(path, cx)
20953            })
20954            .await
20955            .unwrap();
20956        buffers.push(buffer);
20957    }
20958
20959    let multibuffer = cx.new(|cx| {
20960        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
20961        multibuffer.set_all_diff_hunks_expanded(cx);
20962        for buffer in &buffers {
20963            let snapshot = buffer.read(cx).snapshot();
20964            multibuffer.set_excerpts_for_path(
20965                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
20966                buffer.clone(),
20967                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
20968                2,
20969                cx,
20970            );
20971        }
20972        multibuffer
20973    });
20974
20975    let editor = cx.add_window(|window, cx| {
20976        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
20977    });
20978    cx.run_until_parked();
20979
20980    let snapshot = editor
20981        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20982        .unwrap();
20983    let hunks = snapshot
20984        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
20985        .map(|hunk| match hunk {
20986            DisplayDiffHunk::Unfolded {
20987                display_row_range, ..
20988            } => display_row_range,
20989            DisplayDiffHunk::Folded { .. } => unreachable!(),
20990        })
20991        .collect::<Vec<_>>();
20992    assert_eq!(
20993        hunks,
20994        [
20995            DisplayRow(2)..DisplayRow(4),
20996            DisplayRow(7)..DisplayRow(9),
20997            DisplayRow(12)..DisplayRow(14),
20998        ]
20999    );
21000}
21001
21002#[gpui::test]
21003async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21004    init_test(cx, |_| {});
21005
21006    let mut cx = EditorTestContext::new(cx).await;
21007    cx.set_head_text(indoc! { "
21008        one
21009        two
21010        three
21011        four
21012        five
21013        "
21014    });
21015    cx.set_index_text(indoc! { "
21016        one
21017        two
21018        three
21019        four
21020        five
21021        "
21022    });
21023    cx.set_state(indoc! {"
21024        one
21025        TWO
21026        ˇTHREE
21027        FOUR
21028        five
21029    "});
21030    cx.run_until_parked();
21031    cx.update_editor(|editor, window, cx| {
21032        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21033    });
21034    cx.run_until_parked();
21035    cx.assert_index_text(Some(indoc! {"
21036        one
21037        TWO
21038        THREE
21039        FOUR
21040        five
21041    "}));
21042    cx.set_state(indoc! { "
21043        one
21044        TWO
21045        ˇTHREE-HUNDRED
21046        FOUR
21047        five
21048    "});
21049    cx.run_until_parked();
21050    cx.update_editor(|editor, window, cx| {
21051        let snapshot = editor.snapshot(window, cx);
21052        let hunks = editor
21053            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
21054            .collect::<Vec<_>>();
21055        assert_eq!(hunks.len(), 1);
21056        assert_eq!(
21057            hunks[0].status(),
21058            DiffHunkStatus {
21059                kind: DiffHunkStatusKind::Modified,
21060                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21061            }
21062        );
21063
21064        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21065    });
21066    cx.run_until_parked();
21067    cx.assert_index_text(Some(indoc! {"
21068        one
21069        TWO
21070        THREE-HUNDRED
21071        FOUR
21072        five
21073    "}));
21074}
21075
21076#[gpui::test]
21077fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21078    init_test(cx, |_| {});
21079
21080    let editor = cx.add_window(|window, cx| {
21081        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21082        build_editor(buffer, window, cx)
21083    });
21084
21085    let render_args = Arc::new(Mutex::new(None));
21086    let snapshot = editor
21087        .update(cx, |editor, window, cx| {
21088            let snapshot = editor.buffer().read(cx).snapshot(cx);
21089            let range =
21090                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21091
21092            struct RenderArgs {
21093                row: MultiBufferRow,
21094                folded: bool,
21095                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21096            }
21097
21098            let crease = Crease::inline(
21099                range,
21100                FoldPlaceholder::test(),
21101                {
21102                    let toggle_callback = render_args.clone();
21103                    move |row, folded, callback, _window, _cx| {
21104                        *toggle_callback.lock() = Some(RenderArgs {
21105                            row,
21106                            folded,
21107                            callback,
21108                        });
21109                        div()
21110                    }
21111                },
21112                |_row, _folded, _window, _cx| div(),
21113            );
21114
21115            editor.insert_creases(Some(crease), cx);
21116            let snapshot = editor.snapshot(window, cx);
21117            let _div =
21118                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21119            snapshot
21120        })
21121        .unwrap();
21122
21123    let render_args = render_args.lock().take().unwrap();
21124    assert_eq!(render_args.row, MultiBufferRow(1));
21125    assert!(!render_args.folded);
21126    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21127
21128    cx.update_window(*editor, |_, window, cx| {
21129        (render_args.callback)(true, window, cx)
21130    })
21131    .unwrap();
21132    let snapshot = editor
21133        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21134        .unwrap();
21135    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21136
21137    cx.update_window(*editor, |_, window, cx| {
21138        (render_args.callback)(false, window, cx)
21139    })
21140    .unwrap();
21141    let snapshot = editor
21142        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21143        .unwrap();
21144    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21145}
21146
21147#[gpui::test]
21148async fn test_input_text(cx: &mut TestAppContext) {
21149    init_test(cx, |_| {});
21150    let mut cx = EditorTestContext::new(cx).await;
21151
21152    cx.set_state(
21153        &r#"ˇone
21154        two
21155
21156        three
21157        fourˇ
21158        five
21159
21160        siˇx"#
21161            .unindent(),
21162    );
21163
21164    cx.dispatch_action(HandleInput(String::new()));
21165    cx.assert_editor_state(
21166        &r#"ˇone
21167        two
21168
21169        three
21170        fourˇ
21171        five
21172
21173        siˇx"#
21174            .unindent(),
21175    );
21176
21177    cx.dispatch_action(HandleInput("AAAA".to_string()));
21178    cx.assert_editor_state(
21179        &r#"AAAAˇone
21180        two
21181
21182        three
21183        fourAAAAˇ
21184        five
21185
21186        siAAAAˇx"#
21187            .unindent(),
21188    );
21189}
21190
21191#[gpui::test]
21192async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21193    init_test(cx, |_| {});
21194
21195    let mut cx = EditorTestContext::new(cx).await;
21196    cx.set_state(
21197        r#"let foo = 1;
21198let foo = 2;
21199let foo = 3;
21200let fooˇ = 4;
21201let foo = 5;
21202let foo = 6;
21203let foo = 7;
21204let foo = 8;
21205let foo = 9;
21206let foo = 10;
21207let foo = 11;
21208let foo = 12;
21209let foo = 13;
21210let foo = 14;
21211let foo = 15;"#,
21212    );
21213
21214    cx.update_editor(|e, window, cx| {
21215        assert_eq!(
21216            e.next_scroll_position,
21217            NextScrollCursorCenterTopBottom::Center,
21218            "Default next scroll direction is center",
21219        );
21220
21221        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21222        assert_eq!(
21223            e.next_scroll_position,
21224            NextScrollCursorCenterTopBottom::Top,
21225            "After center, next scroll direction should be top",
21226        );
21227
21228        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21229        assert_eq!(
21230            e.next_scroll_position,
21231            NextScrollCursorCenterTopBottom::Bottom,
21232            "After top, next scroll direction should be bottom",
21233        );
21234
21235        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21236        assert_eq!(
21237            e.next_scroll_position,
21238            NextScrollCursorCenterTopBottom::Center,
21239            "After bottom, scrolling should start over",
21240        );
21241
21242        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21243        assert_eq!(
21244            e.next_scroll_position,
21245            NextScrollCursorCenterTopBottom::Top,
21246            "Scrolling continues if retriggered fast enough"
21247        );
21248    });
21249
21250    cx.executor()
21251        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21252    cx.executor().run_until_parked();
21253    cx.update_editor(|e, _, _| {
21254        assert_eq!(
21255            e.next_scroll_position,
21256            NextScrollCursorCenterTopBottom::Center,
21257            "If scrolling is not triggered fast enough, it should reset"
21258        );
21259    });
21260}
21261
21262#[gpui::test]
21263async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21264    init_test(cx, |_| {});
21265    let mut cx = EditorLspTestContext::new_rust(
21266        lsp::ServerCapabilities {
21267            definition_provider: Some(lsp::OneOf::Left(true)),
21268            references_provider: Some(lsp::OneOf::Left(true)),
21269            ..lsp::ServerCapabilities::default()
21270        },
21271        cx,
21272    )
21273    .await;
21274
21275    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21276        let go_to_definition = cx
21277            .lsp
21278            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21279                move |params, _| async move {
21280                    if empty_go_to_definition {
21281                        Ok(None)
21282                    } else {
21283                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21284                            uri: params.text_document_position_params.text_document.uri,
21285                            range: lsp::Range::new(
21286                                lsp::Position::new(4, 3),
21287                                lsp::Position::new(4, 6),
21288                            ),
21289                        })))
21290                    }
21291                },
21292            );
21293        let references = cx
21294            .lsp
21295            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21296                Ok(Some(vec![lsp::Location {
21297                    uri: params.text_document_position.text_document.uri,
21298                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21299                }]))
21300            });
21301        (go_to_definition, references)
21302    };
21303
21304    cx.set_state(
21305        &r#"fn one() {
21306            let mut a = ˇtwo();
21307        }
21308
21309        fn two() {}"#
21310            .unindent(),
21311    );
21312    set_up_lsp_handlers(false, &mut cx);
21313    let navigated = cx
21314        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21315        .await
21316        .expect("Failed to navigate to definition");
21317    assert_eq!(
21318        navigated,
21319        Navigated::Yes,
21320        "Should have navigated to definition from the GetDefinition response"
21321    );
21322    cx.assert_editor_state(
21323        &r#"fn one() {
21324            let mut a = two();
21325        }
21326
21327        fn «twoˇ»() {}"#
21328            .unindent(),
21329    );
21330
21331    let editors = cx.update_workspace(|workspace, _, cx| {
21332        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21333    });
21334    cx.update_editor(|_, _, test_editor_cx| {
21335        assert_eq!(
21336            editors.len(),
21337            1,
21338            "Initially, only one, test, editor should be open in the workspace"
21339        );
21340        assert_eq!(
21341            test_editor_cx.entity(),
21342            editors.last().expect("Asserted len is 1").clone()
21343        );
21344    });
21345
21346    set_up_lsp_handlers(true, &mut cx);
21347    let navigated = cx
21348        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21349        .await
21350        .expect("Failed to navigate to lookup references");
21351    assert_eq!(
21352        navigated,
21353        Navigated::Yes,
21354        "Should have navigated to references as a fallback after empty GoToDefinition response"
21355    );
21356    // We should not change the selections in the existing file,
21357    // if opening another milti buffer with the references
21358    cx.assert_editor_state(
21359        &r#"fn one() {
21360            let mut a = two();
21361        }
21362
21363        fn «twoˇ»() {}"#
21364            .unindent(),
21365    );
21366    let editors = cx.update_workspace(|workspace, _, cx| {
21367        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21368    });
21369    cx.update_editor(|_, _, test_editor_cx| {
21370        assert_eq!(
21371            editors.len(),
21372            2,
21373            "After falling back to references search, we open a new editor with the results"
21374        );
21375        let references_fallback_text = editors
21376            .into_iter()
21377            .find(|new_editor| *new_editor != test_editor_cx.entity())
21378            .expect("Should have one non-test editor now")
21379            .read(test_editor_cx)
21380            .text(test_editor_cx);
21381        assert_eq!(
21382            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21383            "Should use the range from the references response and not the GoToDefinition one"
21384        );
21385    });
21386}
21387
21388#[gpui::test]
21389async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21390    init_test(cx, |_| {});
21391    cx.update(|cx| {
21392        let mut editor_settings = EditorSettings::get_global(cx).clone();
21393        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21394        EditorSettings::override_global(editor_settings, cx);
21395    });
21396    let mut cx = EditorLspTestContext::new_rust(
21397        lsp::ServerCapabilities {
21398            definition_provider: Some(lsp::OneOf::Left(true)),
21399            references_provider: Some(lsp::OneOf::Left(true)),
21400            ..lsp::ServerCapabilities::default()
21401        },
21402        cx,
21403    )
21404    .await;
21405    let original_state = r#"fn one() {
21406        let mut a = ˇtwo();
21407    }
21408
21409    fn two() {}"#
21410        .unindent();
21411    cx.set_state(&original_state);
21412
21413    let mut go_to_definition = cx
21414        .lsp
21415        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21416            move |_, _| async move { Ok(None) },
21417        );
21418    let _references = cx
21419        .lsp
21420        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21421            panic!("Should not call for references with no go to definition fallback")
21422        });
21423
21424    let navigated = cx
21425        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21426        .await
21427        .expect("Failed to navigate to lookup references");
21428    go_to_definition
21429        .next()
21430        .await
21431        .expect("Should have called the go_to_definition handler");
21432
21433    assert_eq!(
21434        navigated,
21435        Navigated::No,
21436        "Should have navigated to references as a fallback after empty GoToDefinition response"
21437    );
21438    cx.assert_editor_state(&original_state);
21439    let editors = cx.update_workspace(|workspace, _, cx| {
21440        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21441    });
21442    cx.update_editor(|_, _, _| {
21443        assert_eq!(
21444            editors.len(),
21445            1,
21446            "After unsuccessful fallback, no other editor should have been opened"
21447        );
21448    });
21449}
21450
21451#[gpui::test]
21452async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21453    init_test(cx, |_| {});
21454    let mut cx = EditorLspTestContext::new_rust(
21455        lsp::ServerCapabilities {
21456            references_provider: Some(lsp::OneOf::Left(true)),
21457            ..lsp::ServerCapabilities::default()
21458        },
21459        cx,
21460    )
21461    .await;
21462
21463    cx.set_state(
21464        &r#"
21465        fn one() {
21466            let mut a = two();
21467        }
21468
21469        fn ˇtwo() {}"#
21470            .unindent(),
21471    );
21472    cx.lsp
21473        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21474            Ok(Some(vec![
21475                lsp::Location {
21476                    uri: params.text_document_position.text_document.uri.clone(),
21477                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21478                },
21479                lsp::Location {
21480                    uri: params.text_document_position.text_document.uri,
21481                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21482                },
21483            ]))
21484        });
21485    let navigated = cx
21486        .update_editor(|editor, window, cx| {
21487            editor.find_all_references(&FindAllReferences, window, cx)
21488        })
21489        .unwrap()
21490        .await
21491        .expect("Failed to navigate to references");
21492    assert_eq!(
21493        navigated,
21494        Navigated::Yes,
21495        "Should have navigated to references from the FindAllReferences response"
21496    );
21497    cx.assert_editor_state(
21498        &r#"fn one() {
21499            let mut a = two();
21500        }
21501
21502        fn ˇtwo() {}"#
21503            .unindent(),
21504    );
21505
21506    let editors = cx.update_workspace(|workspace, _, cx| {
21507        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21508    });
21509    cx.update_editor(|_, _, _| {
21510        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21511    });
21512
21513    cx.set_state(
21514        &r#"fn one() {
21515            let mut a = ˇtwo();
21516        }
21517
21518        fn two() {}"#
21519            .unindent(),
21520    );
21521    let navigated = cx
21522        .update_editor(|editor, window, cx| {
21523            editor.find_all_references(&FindAllReferences, window, cx)
21524        })
21525        .unwrap()
21526        .await
21527        .expect("Failed to navigate to references");
21528    assert_eq!(
21529        navigated,
21530        Navigated::Yes,
21531        "Should have navigated to references from the FindAllReferences response"
21532    );
21533    cx.assert_editor_state(
21534        &r#"fn one() {
21535            let mut a = ˇtwo();
21536        }
21537
21538        fn two() {}"#
21539            .unindent(),
21540    );
21541    let editors = cx.update_workspace(|workspace, _, cx| {
21542        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21543    });
21544    cx.update_editor(|_, _, _| {
21545        assert_eq!(
21546            editors.len(),
21547            2,
21548            "should have re-used the previous multibuffer"
21549        );
21550    });
21551
21552    cx.set_state(
21553        &r#"fn one() {
21554            let mut a = ˇtwo();
21555        }
21556        fn three() {}
21557        fn two() {}"#
21558            .unindent(),
21559    );
21560    cx.lsp
21561        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21562            Ok(Some(vec![
21563                lsp::Location {
21564                    uri: params.text_document_position.text_document.uri.clone(),
21565                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21566                },
21567                lsp::Location {
21568                    uri: params.text_document_position.text_document.uri,
21569                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21570                },
21571            ]))
21572        });
21573    let navigated = cx
21574        .update_editor(|editor, window, cx| {
21575            editor.find_all_references(&FindAllReferences, window, cx)
21576        })
21577        .unwrap()
21578        .await
21579        .expect("Failed to navigate to references");
21580    assert_eq!(
21581        navigated,
21582        Navigated::Yes,
21583        "Should have navigated to references from the FindAllReferences response"
21584    );
21585    cx.assert_editor_state(
21586        &r#"fn one() {
21587                let mut a = ˇtwo();
21588            }
21589            fn three() {}
21590            fn two() {}"#
21591            .unindent(),
21592    );
21593    let editors = cx.update_workspace(|workspace, _, cx| {
21594        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21595    });
21596    cx.update_editor(|_, _, _| {
21597        assert_eq!(
21598            editors.len(),
21599            3,
21600            "should have used a new multibuffer as offsets changed"
21601        );
21602    });
21603}
21604#[gpui::test]
21605async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21606    init_test(cx, |_| {});
21607
21608    let language = Arc::new(Language::new(
21609        LanguageConfig::default(),
21610        Some(tree_sitter_rust::LANGUAGE.into()),
21611    ));
21612
21613    let text = r#"
21614        #[cfg(test)]
21615        mod tests() {
21616            #[test]
21617            fn runnable_1() {
21618                let a = 1;
21619            }
21620
21621            #[test]
21622            fn runnable_2() {
21623                let a = 1;
21624                let b = 2;
21625            }
21626        }
21627    "#
21628    .unindent();
21629
21630    let fs = FakeFs::new(cx.executor());
21631    fs.insert_file("/file.rs", Default::default()).await;
21632
21633    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21634    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21635    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21636    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21637    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21638
21639    let editor = cx.new_window_entity(|window, cx| {
21640        Editor::new(
21641            EditorMode::full(),
21642            multi_buffer,
21643            Some(project.clone()),
21644            window,
21645            cx,
21646        )
21647    });
21648
21649    editor.update_in(cx, |editor, window, cx| {
21650        let snapshot = editor.buffer().read(cx).snapshot(cx);
21651        editor.tasks.insert(
21652            (buffer.read(cx).remote_id(), 3),
21653            RunnableTasks {
21654                templates: vec![],
21655                offset: snapshot.anchor_before(43),
21656                column: 0,
21657                extra_variables: HashMap::default(),
21658                context_range: BufferOffset(43)..BufferOffset(85),
21659            },
21660        );
21661        editor.tasks.insert(
21662            (buffer.read(cx).remote_id(), 8),
21663            RunnableTasks {
21664                templates: vec![],
21665                offset: snapshot.anchor_before(86),
21666                column: 0,
21667                extra_variables: HashMap::default(),
21668                context_range: BufferOffset(86)..BufferOffset(191),
21669            },
21670        );
21671
21672        // Test finding task when cursor is inside function body
21673        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21674            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21675        });
21676        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21677        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21678
21679        // Test finding task when cursor is on function name
21680        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21681            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21682        });
21683        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21684        assert_eq!(row, 8, "Should find task when cursor is on function name");
21685    });
21686}
21687
21688#[gpui::test]
21689async fn test_folding_buffers(cx: &mut TestAppContext) {
21690    init_test(cx, |_| {});
21691
21692    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21693    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21694    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21695
21696    let fs = FakeFs::new(cx.executor());
21697    fs.insert_tree(
21698        path!("/a"),
21699        json!({
21700            "first.rs": sample_text_1,
21701            "second.rs": sample_text_2,
21702            "third.rs": sample_text_3,
21703        }),
21704    )
21705    .await;
21706    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21707    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21708    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21709    let worktree = project.update(cx, |project, cx| {
21710        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21711        assert_eq!(worktrees.len(), 1);
21712        worktrees.pop().unwrap()
21713    });
21714    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21715
21716    let buffer_1 = project
21717        .update(cx, |project, cx| {
21718            project.open_buffer((worktree_id, "first.rs"), cx)
21719        })
21720        .await
21721        .unwrap();
21722    let buffer_2 = project
21723        .update(cx, |project, cx| {
21724            project.open_buffer((worktree_id, "second.rs"), cx)
21725        })
21726        .await
21727        .unwrap();
21728    let buffer_3 = project
21729        .update(cx, |project, cx| {
21730            project.open_buffer((worktree_id, "third.rs"), cx)
21731        })
21732        .await
21733        .unwrap();
21734
21735    let multi_buffer = cx.new(|cx| {
21736        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21737        multi_buffer.push_excerpts(
21738            buffer_1.clone(),
21739            [
21740                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21741                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21742                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21743            ],
21744            cx,
21745        );
21746        multi_buffer.push_excerpts(
21747            buffer_2.clone(),
21748            [
21749                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21750                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21751                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21752            ],
21753            cx,
21754        );
21755        multi_buffer.push_excerpts(
21756            buffer_3.clone(),
21757            [
21758                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21759                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21760                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21761            ],
21762            cx,
21763        );
21764        multi_buffer
21765    });
21766    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21767        Editor::new(
21768            EditorMode::full(),
21769            multi_buffer.clone(),
21770            Some(project.clone()),
21771            window,
21772            cx,
21773        )
21774    });
21775
21776    assert_eq!(
21777        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21778        "\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",
21779    );
21780
21781    multi_buffer_editor.update(cx, |editor, cx| {
21782        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21783    });
21784    assert_eq!(
21785        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21786        "\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",
21787        "After folding the first buffer, its text should not be displayed"
21788    );
21789
21790    multi_buffer_editor.update(cx, |editor, cx| {
21791        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21792    });
21793    assert_eq!(
21794        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21795        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21796        "After folding the second buffer, its text should not be displayed"
21797    );
21798
21799    multi_buffer_editor.update(cx, |editor, cx| {
21800        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21801    });
21802    assert_eq!(
21803        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21804        "\n\n\n\n\n",
21805        "After folding the third buffer, its text should not be displayed"
21806    );
21807
21808    // Emulate selection inside the fold logic, that should work
21809    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21810        editor
21811            .snapshot(window, cx)
21812            .next_line_boundary(Point::new(0, 4));
21813    });
21814
21815    multi_buffer_editor.update(cx, |editor, cx| {
21816        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21817    });
21818    assert_eq!(
21819        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21820        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21821        "After unfolding the second buffer, its text should be displayed"
21822    );
21823
21824    // Typing inside of buffer 1 causes that buffer to be unfolded.
21825    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21826        assert_eq!(
21827            multi_buffer
21828                .read(cx)
21829                .snapshot(cx)
21830                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21831                .collect::<String>(),
21832            "bbbb"
21833        );
21834        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21835            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
21836        });
21837        editor.handle_input("B", window, cx);
21838    });
21839
21840    assert_eq!(
21841        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21842        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21843        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
21844    );
21845
21846    multi_buffer_editor.update(cx, |editor, cx| {
21847        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21848    });
21849    assert_eq!(
21850        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21851        "\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",
21852        "After unfolding the all buffers, all original text should be displayed"
21853    );
21854}
21855
21856#[gpui::test]
21857async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
21858    init_test(cx, |_| {});
21859
21860    let sample_text_1 = "1111\n2222\n3333".to_string();
21861    let sample_text_2 = "4444\n5555\n6666".to_string();
21862    let sample_text_3 = "7777\n8888\n9999".to_string();
21863
21864    let fs = FakeFs::new(cx.executor());
21865    fs.insert_tree(
21866        path!("/a"),
21867        json!({
21868            "first.rs": sample_text_1,
21869            "second.rs": sample_text_2,
21870            "third.rs": sample_text_3,
21871        }),
21872    )
21873    .await;
21874    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21875    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21876    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21877    let worktree = project.update(cx, |project, cx| {
21878        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21879        assert_eq!(worktrees.len(), 1);
21880        worktrees.pop().unwrap()
21881    });
21882    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21883
21884    let buffer_1 = project
21885        .update(cx, |project, cx| {
21886            project.open_buffer((worktree_id, "first.rs"), cx)
21887        })
21888        .await
21889        .unwrap();
21890    let buffer_2 = project
21891        .update(cx, |project, cx| {
21892            project.open_buffer((worktree_id, "second.rs"), cx)
21893        })
21894        .await
21895        .unwrap();
21896    let buffer_3 = project
21897        .update(cx, |project, cx| {
21898            project.open_buffer((worktree_id, "third.rs"), cx)
21899        })
21900        .await
21901        .unwrap();
21902
21903    let multi_buffer = cx.new(|cx| {
21904        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21905        multi_buffer.push_excerpts(
21906            buffer_1.clone(),
21907            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21908            cx,
21909        );
21910        multi_buffer.push_excerpts(
21911            buffer_2.clone(),
21912            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21913            cx,
21914        );
21915        multi_buffer.push_excerpts(
21916            buffer_3.clone(),
21917            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21918            cx,
21919        );
21920        multi_buffer
21921    });
21922
21923    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21924        Editor::new(
21925            EditorMode::full(),
21926            multi_buffer,
21927            Some(project.clone()),
21928            window,
21929            cx,
21930        )
21931    });
21932
21933    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
21934    assert_eq!(
21935        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21936        full_text,
21937    );
21938
21939    multi_buffer_editor.update(cx, |editor, cx| {
21940        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21941    });
21942    assert_eq!(
21943        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21944        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
21945        "After folding the first buffer, its text should not be displayed"
21946    );
21947
21948    multi_buffer_editor.update(cx, |editor, cx| {
21949        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21950    });
21951
21952    assert_eq!(
21953        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21954        "\n\n\n\n\n\n7777\n8888\n9999",
21955        "After folding the second buffer, its text should not be displayed"
21956    );
21957
21958    multi_buffer_editor.update(cx, |editor, cx| {
21959        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21960    });
21961    assert_eq!(
21962        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21963        "\n\n\n\n\n",
21964        "After folding the third buffer, its text should not be displayed"
21965    );
21966
21967    multi_buffer_editor.update(cx, |editor, cx| {
21968        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21969    });
21970    assert_eq!(
21971        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21972        "\n\n\n\n4444\n5555\n6666\n\n",
21973        "After unfolding the second buffer, its text should be displayed"
21974    );
21975
21976    multi_buffer_editor.update(cx, |editor, cx| {
21977        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
21978    });
21979    assert_eq!(
21980        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21981        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
21982        "After unfolding the first buffer, its text should be displayed"
21983    );
21984
21985    multi_buffer_editor.update(cx, |editor, cx| {
21986        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21987    });
21988    assert_eq!(
21989        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21990        full_text,
21991        "After unfolding all buffers, all original text should be displayed"
21992    );
21993}
21994
21995#[gpui::test]
21996async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
21997    init_test(cx, |_| {});
21998
21999    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22000
22001    let fs = FakeFs::new(cx.executor());
22002    fs.insert_tree(
22003        path!("/a"),
22004        json!({
22005            "main.rs": sample_text,
22006        }),
22007    )
22008    .await;
22009    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22010    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22011    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22012    let worktree = project.update(cx, |project, cx| {
22013        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22014        assert_eq!(worktrees.len(), 1);
22015        worktrees.pop().unwrap()
22016    });
22017    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22018
22019    let buffer_1 = project
22020        .update(cx, |project, cx| {
22021            project.open_buffer((worktree_id, "main.rs"), cx)
22022        })
22023        .await
22024        .unwrap();
22025
22026    let multi_buffer = cx.new(|cx| {
22027        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22028        multi_buffer.push_excerpts(
22029            buffer_1.clone(),
22030            [ExcerptRange::new(
22031                Point::new(0, 0)
22032                    ..Point::new(
22033                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22034                        0,
22035                    ),
22036            )],
22037            cx,
22038        );
22039        multi_buffer
22040    });
22041    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22042        Editor::new(
22043            EditorMode::full(),
22044            multi_buffer,
22045            Some(project.clone()),
22046            window,
22047            cx,
22048        )
22049    });
22050
22051    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22052    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22053        enum TestHighlight {}
22054        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22055        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22056        editor.highlight_text::<TestHighlight>(
22057            vec![highlight_range.clone()],
22058            HighlightStyle::color(Hsla::green()),
22059            cx,
22060        );
22061        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22062            s.select_ranges(Some(highlight_range))
22063        });
22064    });
22065
22066    let full_text = format!("\n\n{sample_text}");
22067    assert_eq!(
22068        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22069        full_text,
22070    );
22071}
22072
22073#[gpui::test]
22074async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22075    init_test(cx, |_| {});
22076    cx.update(|cx| {
22077        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22078            "keymaps/default-linux.json",
22079            cx,
22080        )
22081        .unwrap();
22082        cx.bind_keys(default_key_bindings);
22083    });
22084
22085    let (editor, cx) = cx.add_window_view(|window, cx| {
22086        let multi_buffer = MultiBuffer::build_multi(
22087            [
22088                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22089                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22090                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22091                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22092            ],
22093            cx,
22094        );
22095        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22096
22097        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22098        // fold all but the second buffer, so that we test navigating between two
22099        // adjacent folded buffers, as well as folded buffers at the start and
22100        // end the multibuffer
22101        editor.fold_buffer(buffer_ids[0], cx);
22102        editor.fold_buffer(buffer_ids[2], cx);
22103        editor.fold_buffer(buffer_ids[3], cx);
22104
22105        editor
22106    });
22107    cx.simulate_resize(size(px(1000.), px(1000.)));
22108
22109    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22110    cx.assert_excerpts_with_selections(indoc! {"
22111        [EXCERPT]
22112        ˇ[FOLDED]
22113        [EXCERPT]
22114        a1
22115        b1
22116        [EXCERPT]
22117        [FOLDED]
22118        [EXCERPT]
22119        [FOLDED]
22120        "
22121    });
22122    cx.simulate_keystroke("down");
22123    cx.assert_excerpts_with_selections(indoc! {"
22124        [EXCERPT]
22125        [FOLDED]
22126        [EXCERPT]
22127        ˇa1
22128        b1
22129        [EXCERPT]
22130        [FOLDED]
22131        [EXCERPT]
22132        [FOLDED]
22133        "
22134    });
22135    cx.simulate_keystroke("down");
22136    cx.assert_excerpts_with_selections(indoc! {"
22137        [EXCERPT]
22138        [FOLDED]
22139        [EXCERPT]
22140        a1
22141        ˇb1
22142        [EXCERPT]
22143        [FOLDED]
22144        [EXCERPT]
22145        [FOLDED]
22146        "
22147    });
22148    cx.simulate_keystroke("down");
22149    cx.assert_excerpts_with_selections(indoc! {"
22150        [EXCERPT]
22151        [FOLDED]
22152        [EXCERPT]
22153        a1
22154        b1
22155        ˇ[EXCERPT]
22156        [FOLDED]
22157        [EXCERPT]
22158        [FOLDED]
22159        "
22160    });
22161    cx.simulate_keystroke("down");
22162    cx.assert_excerpts_with_selections(indoc! {"
22163        [EXCERPT]
22164        [FOLDED]
22165        [EXCERPT]
22166        a1
22167        b1
22168        [EXCERPT]
22169        ˇ[FOLDED]
22170        [EXCERPT]
22171        [FOLDED]
22172        "
22173    });
22174    for _ in 0..5 {
22175        cx.simulate_keystroke("down");
22176        cx.assert_excerpts_with_selections(indoc! {"
22177            [EXCERPT]
22178            [FOLDED]
22179            [EXCERPT]
22180            a1
22181            b1
22182            [EXCERPT]
22183            [FOLDED]
22184            [EXCERPT]
22185            ˇ[FOLDED]
22186            "
22187        });
22188    }
22189
22190    cx.simulate_keystroke("up");
22191    cx.assert_excerpts_with_selections(indoc! {"
22192        [EXCERPT]
22193        [FOLDED]
22194        [EXCERPT]
22195        a1
22196        b1
22197        [EXCERPT]
22198        ˇ[FOLDED]
22199        [EXCERPT]
22200        [FOLDED]
22201        "
22202    });
22203    cx.simulate_keystroke("up");
22204    cx.assert_excerpts_with_selections(indoc! {"
22205        [EXCERPT]
22206        [FOLDED]
22207        [EXCERPT]
22208        a1
22209        b1
22210        ˇ[EXCERPT]
22211        [FOLDED]
22212        [EXCERPT]
22213        [FOLDED]
22214        "
22215    });
22216    cx.simulate_keystroke("up");
22217    cx.assert_excerpts_with_selections(indoc! {"
22218        [EXCERPT]
22219        [FOLDED]
22220        [EXCERPT]
22221        a1
22222        ˇb1
22223        [EXCERPT]
22224        [FOLDED]
22225        [EXCERPT]
22226        [FOLDED]
22227        "
22228    });
22229    cx.simulate_keystroke("up");
22230    cx.assert_excerpts_with_selections(indoc! {"
22231        [EXCERPT]
22232        [FOLDED]
22233        [EXCERPT]
22234        ˇa1
22235        b1
22236        [EXCERPT]
22237        [FOLDED]
22238        [EXCERPT]
22239        [FOLDED]
22240        "
22241    });
22242    for _ in 0..5 {
22243        cx.simulate_keystroke("up");
22244        cx.assert_excerpts_with_selections(indoc! {"
22245            [EXCERPT]
22246            ˇ[FOLDED]
22247            [EXCERPT]
22248            a1
22249            b1
22250            [EXCERPT]
22251            [FOLDED]
22252            [EXCERPT]
22253            [FOLDED]
22254            "
22255        });
22256    }
22257}
22258
22259#[gpui::test]
22260async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22261    init_test(cx, |_| {});
22262
22263    // Simple insertion
22264    assert_highlighted_edits(
22265        "Hello, world!",
22266        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22267        true,
22268        cx,
22269        |highlighted_edits, cx| {
22270            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22271            assert_eq!(highlighted_edits.highlights.len(), 1);
22272            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22273            assert_eq!(
22274                highlighted_edits.highlights[0].1.background_color,
22275                Some(cx.theme().status().created_background)
22276            );
22277        },
22278    )
22279    .await;
22280
22281    // Replacement
22282    assert_highlighted_edits(
22283        "This is a test.",
22284        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22285        false,
22286        cx,
22287        |highlighted_edits, cx| {
22288            assert_eq!(highlighted_edits.text, "That is a test.");
22289            assert_eq!(highlighted_edits.highlights.len(), 1);
22290            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22291            assert_eq!(
22292                highlighted_edits.highlights[0].1.background_color,
22293                Some(cx.theme().status().created_background)
22294            );
22295        },
22296    )
22297    .await;
22298
22299    // Multiple edits
22300    assert_highlighted_edits(
22301        "Hello, world!",
22302        vec![
22303            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22304            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22305        ],
22306        false,
22307        cx,
22308        |highlighted_edits, cx| {
22309            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22310            assert_eq!(highlighted_edits.highlights.len(), 2);
22311            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22312            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22313            assert_eq!(
22314                highlighted_edits.highlights[0].1.background_color,
22315                Some(cx.theme().status().created_background)
22316            );
22317            assert_eq!(
22318                highlighted_edits.highlights[1].1.background_color,
22319                Some(cx.theme().status().created_background)
22320            );
22321        },
22322    )
22323    .await;
22324
22325    // Multiple lines with edits
22326    assert_highlighted_edits(
22327        "First line\nSecond line\nThird line\nFourth line",
22328        vec![
22329            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22330            (
22331                Point::new(2, 0)..Point::new(2, 10),
22332                "New third line".to_string(),
22333            ),
22334            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22335        ],
22336        false,
22337        cx,
22338        |highlighted_edits, cx| {
22339            assert_eq!(
22340                highlighted_edits.text,
22341                "Second modified\nNew third line\nFourth updated line"
22342            );
22343            assert_eq!(highlighted_edits.highlights.len(), 3);
22344            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22345            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22346            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22347            for highlight in &highlighted_edits.highlights {
22348                assert_eq!(
22349                    highlight.1.background_color,
22350                    Some(cx.theme().status().created_background)
22351                );
22352            }
22353        },
22354    )
22355    .await;
22356}
22357
22358#[gpui::test]
22359async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22360    init_test(cx, |_| {});
22361
22362    // Deletion
22363    assert_highlighted_edits(
22364        "Hello, world!",
22365        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22366        true,
22367        cx,
22368        |highlighted_edits, cx| {
22369            assert_eq!(highlighted_edits.text, "Hello, world!");
22370            assert_eq!(highlighted_edits.highlights.len(), 1);
22371            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22372            assert_eq!(
22373                highlighted_edits.highlights[0].1.background_color,
22374                Some(cx.theme().status().deleted_background)
22375            );
22376        },
22377    )
22378    .await;
22379
22380    // Insertion
22381    assert_highlighted_edits(
22382        "Hello, world!",
22383        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22384        true,
22385        cx,
22386        |highlighted_edits, cx| {
22387            assert_eq!(highlighted_edits.highlights.len(), 1);
22388            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22389            assert_eq!(
22390                highlighted_edits.highlights[0].1.background_color,
22391                Some(cx.theme().status().created_background)
22392            );
22393        },
22394    )
22395    .await;
22396}
22397
22398async fn assert_highlighted_edits(
22399    text: &str,
22400    edits: Vec<(Range<Point>, String)>,
22401    include_deletions: bool,
22402    cx: &mut TestAppContext,
22403    assertion_fn: impl Fn(HighlightedText, &App),
22404) {
22405    let window = cx.add_window(|window, cx| {
22406        let buffer = MultiBuffer::build_simple(text, cx);
22407        Editor::new(EditorMode::full(), buffer, None, window, cx)
22408    });
22409    let cx = &mut VisualTestContext::from_window(*window, cx);
22410
22411    let (buffer, snapshot) = window
22412        .update(cx, |editor, _window, cx| {
22413            (
22414                editor.buffer().clone(),
22415                editor.buffer().read(cx).snapshot(cx),
22416            )
22417        })
22418        .unwrap();
22419
22420    let edits = edits
22421        .into_iter()
22422        .map(|(range, edit)| {
22423            (
22424                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22425                edit,
22426            )
22427        })
22428        .collect::<Vec<_>>();
22429
22430    let text_anchor_edits = edits
22431        .clone()
22432        .into_iter()
22433        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22434        .collect::<Vec<_>>();
22435
22436    let edit_preview = window
22437        .update(cx, |_, _window, cx| {
22438            buffer
22439                .read(cx)
22440                .as_singleton()
22441                .unwrap()
22442                .read(cx)
22443                .preview_edits(text_anchor_edits.into(), cx)
22444        })
22445        .unwrap()
22446        .await;
22447
22448    cx.update(|_window, cx| {
22449        let highlighted_edits = edit_prediction_edit_text(
22450            snapshot.as_singleton().unwrap().2,
22451            &edits,
22452            &edit_preview,
22453            include_deletions,
22454            cx,
22455        );
22456        assertion_fn(highlighted_edits, cx)
22457    });
22458}
22459
22460#[track_caller]
22461fn assert_breakpoint(
22462    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22463    path: &Arc<Path>,
22464    expected: Vec<(u32, Breakpoint)>,
22465) {
22466    if expected.is_empty() {
22467        assert!(!breakpoints.contains_key(path), "{}", path.display());
22468    } else {
22469        let mut breakpoint = breakpoints
22470            .get(path)
22471            .unwrap()
22472            .iter()
22473            .map(|breakpoint| {
22474                (
22475                    breakpoint.row,
22476                    Breakpoint {
22477                        message: breakpoint.message.clone(),
22478                        state: breakpoint.state,
22479                        condition: breakpoint.condition.clone(),
22480                        hit_condition: breakpoint.hit_condition.clone(),
22481                    },
22482                )
22483            })
22484            .collect::<Vec<_>>();
22485
22486        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22487
22488        assert_eq!(expected, breakpoint);
22489    }
22490}
22491
22492fn add_log_breakpoint_at_cursor(
22493    editor: &mut Editor,
22494    log_message: &str,
22495    window: &mut Window,
22496    cx: &mut Context<Editor>,
22497) {
22498    let (anchor, bp) = editor
22499        .breakpoints_at_cursors(window, cx)
22500        .first()
22501        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22502        .unwrap_or_else(|| {
22503            let cursor_position: Point = editor.selections.newest(cx).head();
22504
22505            let breakpoint_position = editor
22506                .snapshot(window, cx)
22507                .display_snapshot
22508                .buffer_snapshot
22509                .anchor_before(Point::new(cursor_position.row, 0));
22510
22511            (breakpoint_position, Breakpoint::new_log(log_message))
22512        });
22513
22514    editor.edit_breakpoint_at_anchor(
22515        anchor,
22516        bp,
22517        BreakpointEditAction::EditLogMessage(log_message.into()),
22518        cx,
22519    );
22520}
22521
22522#[gpui::test]
22523async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22524    init_test(cx, |_| {});
22525
22526    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22527    let fs = FakeFs::new(cx.executor());
22528    fs.insert_tree(
22529        path!("/a"),
22530        json!({
22531            "main.rs": sample_text,
22532        }),
22533    )
22534    .await;
22535    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22536    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22537    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22538
22539    let fs = FakeFs::new(cx.executor());
22540    fs.insert_tree(
22541        path!("/a"),
22542        json!({
22543            "main.rs": sample_text,
22544        }),
22545    )
22546    .await;
22547    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22548    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22549    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22550    let worktree_id = workspace
22551        .update(cx, |workspace, _window, cx| {
22552            workspace.project().update(cx, |project, cx| {
22553                project.worktrees(cx).next().unwrap().read(cx).id()
22554            })
22555        })
22556        .unwrap();
22557
22558    let buffer = project
22559        .update(cx, |project, cx| {
22560            project.open_buffer((worktree_id, "main.rs"), cx)
22561        })
22562        .await
22563        .unwrap();
22564
22565    let (editor, cx) = cx.add_window_view(|window, cx| {
22566        Editor::new(
22567            EditorMode::full(),
22568            MultiBuffer::build_from_buffer(buffer, cx),
22569            Some(project.clone()),
22570            window,
22571            cx,
22572        )
22573    });
22574
22575    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22576    let abs_path = project.read_with(cx, |project, cx| {
22577        project
22578            .absolute_path(&project_path, cx)
22579            .map(Arc::from)
22580            .unwrap()
22581    });
22582
22583    // assert we can add breakpoint on the first line
22584    editor.update_in(cx, |editor, window, cx| {
22585        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22586        editor.move_to_end(&MoveToEnd, window, cx);
22587        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22588    });
22589
22590    let breakpoints = editor.update(cx, |editor, cx| {
22591        editor
22592            .breakpoint_store()
22593            .as_ref()
22594            .unwrap()
22595            .read(cx)
22596            .all_source_breakpoints(cx)
22597    });
22598
22599    assert_eq!(1, breakpoints.len());
22600    assert_breakpoint(
22601        &breakpoints,
22602        &abs_path,
22603        vec![
22604            (0, Breakpoint::new_standard()),
22605            (3, Breakpoint::new_standard()),
22606        ],
22607    );
22608
22609    editor.update_in(cx, |editor, window, cx| {
22610        editor.move_to_beginning(&MoveToBeginning, window, cx);
22611        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22612    });
22613
22614    let breakpoints = editor.update(cx, |editor, cx| {
22615        editor
22616            .breakpoint_store()
22617            .as_ref()
22618            .unwrap()
22619            .read(cx)
22620            .all_source_breakpoints(cx)
22621    });
22622
22623    assert_eq!(1, breakpoints.len());
22624    assert_breakpoint(
22625        &breakpoints,
22626        &abs_path,
22627        vec![(3, Breakpoint::new_standard())],
22628    );
22629
22630    editor.update_in(cx, |editor, window, cx| {
22631        editor.move_to_end(&MoveToEnd, window, cx);
22632        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22633    });
22634
22635    let breakpoints = editor.update(cx, |editor, cx| {
22636        editor
22637            .breakpoint_store()
22638            .as_ref()
22639            .unwrap()
22640            .read(cx)
22641            .all_source_breakpoints(cx)
22642    });
22643
22644    assert_eq!(0, breakpoints.len());
22645    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22646}
22647
22648#[gpui::test]
22649async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22650    init_test(cx, |_| {});
22651
22652    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22653
22654    let fs = FakeFs::new(cx.executor());
22655    fs.insert_tree(
22656        path!("/a"),
22657        json!({
22658            "main.rs": sample_text,
22659        }),
22660    )
22661    .await;
22662    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22663    let (workspace, cx) =
22664        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22665
22666    let worktree_id = workspace.update(cx, |workspace, cx| {
22667        workspace.project().update(cx, |project, cx| {
22668            project.worktrees(cx).next().unwrap().read(cx).id()
22669        })
22670    });
22671
22672    let buffer = project
22673        .update(cx, |project, cx| {
22674            project.open_buffer((worktree_id, "main.rs"), cx)
22675        })
22676        .await
22677        .unwrap();
22678
22679    let (editor, cx) = cx.add_window_view(|window, cx| {
22680        Editor::new(
22681            EditorMode::full(),
22682            MultiBuffer::build_from_buffer(buffer, cx),
22683            Some(project.clone()),
22684            window,
22685            cx,
22686        )
22687    });
22688
22689    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22690    let abs_path = project.read_with(cx, |project, cx| {
22691        project
22692            .absolute_path(&project_path, cx)
22693            .map(Arc::from)
22694            .unwrap()
22695    });
22696
22697    editor.update_in(cx, |editor, window, cx| {
22698        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22699    });
22700
22701    let breakpoints = editor.update(cx, |editor, cx| {
22702        editor
22703            .breakpoint_store()
22704            .as_ref()
22705            .unwrap()
22706            .read(cx)
22707            .all_source_breakpoints(cx)
22708    });
22709
22710    assert_breakpoint(
22711        &breakpoints,
22712        &abs_path,
22713        vec![(0, Breakpoint::new_log("hello world"))],
22714    );
22715
22716    // Removing a log message from a log breakpoint should remove it
22717    editor.update_in(cx, |editor, window, cx| {
22718        add_log_breakpoint_at_cursor(editor, "", window, cx);
22719    });
22720
22721    let breakpoints = editor.update(cx, |editor, cx| {
22722        editor
22723            .breakpoint_store()
22724            .as_ref()
22725            .unwrap()
22726            .read(cx)
22727            .all_source_breakpoints(cx)
22728    });
22729
22730    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22731
22732    editor.update_in(cx, |editor, window, cx| {
22733        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22734        editor.move_to_end(&MoveToEnd, window, cx);
22735        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22736        // Not adding a log message to a standard breakpoint shouldn't remove it
22737        add_log_breakpoint_at_cursor(editor, "", window, cx);
22738    });
22739
22740    let breakpoints = editor.update(cx, |editor, cx| {
22741        editor
22742            .breakpoint_store()
22743            .as_ref()
22744            .unwrap()
22745            .read(cx)
22746            .all_source_breakpoints(cx)
22747    });
22748
22749    assert_breakpoint(
22750        &breakpoints,
22751        &abs_path,
22752        vec![
22753            (0, Breakpoint::new_standard()),
22754            (3, Breakpoint::new_standard()),
22755        ],
22756    );
22757
22758    editor.update_in(cx, |editor, window, cx| {
22759        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22760    });
22761
22762    let breakpoints = editor.update(cx, |editor, cx| {
22763        editor
22764            .breakpoint_store()
22765            .as_ref()
22766            .unwrap()
22767            .read(cx)
22768            .all_source_breakpoints(cx)
22769    });
22770
22771    assert_breakpoint(
22772        &breakpoints,
22773        &abs_path,
22774        vec![
22775            (0, Breakpoint::new_standard()),
22776            (3, Breakpoint::new_log("hello world")),
22777        ],
22778    );
22779
22780    editor.update_in(cx, |editor, window, cx| {
22781        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22782    });
22783
22784    let breakpoints = editor.update(cx, |editor, cx| {
22785        editor
22786            .breakpoint_store()
22787            .as_ref()
22788            .unwrap()
22789            .read(cx)
22790            .all_source_breakpoints(cx)
22791    });
22792
22793    assert_breakpoint(
22794        &breakpoints,
22795        &abs_path,
22796        vec![
22797            (0, Breakpoint::new_standard()),
22798            (3, Breakpoint::new_log("hello Earth!!")),
22799        ],
22800    );
22801}
22802
22803/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22804/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22805/// or when breakpoints were placed out of order. This tests for a regression too
22806#[gpui::test]
22807async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22808    init_test(cx, |_| {});
22809
22810    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22811    let fs = FakeFs::new(cx.executor());
22812    fs.insert_tree(
22813        path!("/a"),
22814        json!({
22815            "main.rs": sample_text,
22816        }),
22817    )
22818    .await;
22819    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22820    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22821    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22822
22823    let fs = FakeFs::new(cx.executor());
22824    fs.insert_tree(
22825        path!("/a"),
22826        json!({
22827            "main.rs": sample_text,
22828        }),
22829    )
22830    .await;
22831    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22832    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22833    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22834    let worktree_id = workspace
22835        .update(cx, |workspace, _window, cx| {
22836            workspace.project().update(cx, |project, cx| {
22837                project.worktrees(cx).next().unwrap().read(cx).id()
22838            })
22839        })
22840        .unwrap();
22841
22842    let buffer = project
22843        .update(cx, |project, cx| {
22844            project.open_buffer((worktree_id, "main.rs"), cx)
22845        })
22846        .await
22847        .unwrap();
22848
22849    let (editor, cx) = cx.add_window_view(|window, cx| {
22850        Editor::new(
22851            EditorMode::full(),
22852            MultiBuffer::build_from_buffer(buffer, cx),
22853            Some(project.clone()),
22854            window,
22855            cx,
22856        )
22857    });
22858
22859    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22860    let abs_path = project.read_with(cx, |project, cx| {
22861        project
22862            .absolute_path(&project_path, cx)
22863            .map(Arc::from)
22864            .unwrap()
22865    });
22866
22867    // assert we can add breakpoint on the first line
22868    editor.update_in(cx, |editor, window, cx| {
22869        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22870        editor.move_to_end(&MoveToEnd, window, cx);
22871        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22872        editor.move_up(&MoveUp, window, cx);
22873        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22874    });
22875
22876    let breakpoints = editor.update(cx, |editor, cx| {
22877        editor
22878            .breakpoint_store()
22879            .as_ref()
22880            .unwrap()
22881            .read(cx)
22882            .all_source_breakpoints(cx)
22883    });
22884
22885    assert_eq!(1, breakpoints.len());
22886    assert_breakpoint(
22887        &breakpoints,
22888        &abs_path,
22889        vec![
22890            (0, Breakpoint::new_standard()),
22891            (2, Breakpoint::new_standard()),
22892            (3, Breakpoint::new_standard()),
22893        ],
22894    );
22895
22896    editor.update_in(cx, |editor, window, cx| {
22897        editor.move_to_beginning(&MoveToBeginning, window, cx);
22898        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22899        editor.move_to_end(&MoveToEnd, window, cx);
22900        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22901        // Disabling a breakpoint that doesn't exist should do nothing
22902        editor.move_up(&MoveUp, window, cx);
22903        editor.move_up(&MoveUp, window, cx);
22904        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22905    });
22906
22907    let breakpoints = editor.update(cx, |editor, cx| {
22908        editor
22909            .breakpoint_store()
22910            .as_ref()
22911            .unwrap()
22912            .read(cx)
22913            .all_source_breakpoints(cx)
22914    });
22915
22916    let disable_breakpoint = {
22917        let mut bp = Breakpoint::new_standard();
22918        bp.state = BreakpointState::Disabled;
22919        bp
22920    };
22921
22922    assert_eq!(1, breakpoints.len());
22923    assert_breakpoint(
22924        &breakpoints,
22925        &abs_path,
22926        vec![
22927            (0, disable_breakpoint.clone()),
22928            (2, Breakpoint::new_standard()),
22929            (3, disable_breakpoint.clone()),
22930        ],
22931    );
22932
22933    editor.update_in(cx, |editor, window, cx| {
22934        editor.move_to_beginning(&MoveToBeginning, window, cx);
22935        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22936        editor.move_to_end(&MoveToEnd, window, cx);
22937        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22938        editor.move_up(&MoveUp, window, cx);
22939        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22940    });
22941
22942    let breakpoints = editor.update(cx, |editor, cx| {
22943        editor
22944            .breakpoint_store()
22945            .as_ref()
22946            .unwrap()
22947            .read(cx)
22948            .all_source_breakpoints(cx)
22949    });
22950
22951    assert_eq!(1, breakpoints.len());
22952    assert_breakpoint(
22953        &breakpoints,
22954        &abs_path,
22955        vec![
22956            (0, Breakpoint::new_standard()),
22957            (2, disable_breakpoint),
22958            (3, Breakpoint::new_standard()),
22959        ],
22960    );
22961}
22962
22963#[gpui::test]
22964async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
22965    init_test(cx, |_| {});
22966    let capabilities = lsp::ServerCapabilities {
22967        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
22968            prepare_provider: Some(true),
22969            work_done_progress_options: Default::default(),
22970        })),
22971        ..Default::default()
22972    };
22973    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22974
22975    cx.set_state(indoc! {"
22976        struct Fˇoo {}
22977    "});
22978
22979    cx.update_editor(|editor, _, cx| {
22980        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22981        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22982        editor.highlight_background::<DocumentHighlightRead>(
22983            &[highlight_range],
22984            |theme| theme.colors().editor_document_highlight_read_background,
22985            cx,
22986        );
22987    });
22988
22989    let mut prepare_rename_handler = cx
22990        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
22991            move |_, _, _| async move {
22992                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
22993                    start: lsp::Position {
22994                        line: 0,
22995                        character: 7,
22996                    },
22997                    end: lsp::Position {
22998                        line: 0,
22999                        character: 10,
23000                    },
23001                })))
23002            },
23003        );
23004    let prepare_rename_task = cx
23005        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23006        .expect("Prepare rename was not started");
23007    prepare_rename_handler.next().await.unwrap();
23008    prepare_rename_task.await.expect("Prepare rename failed");
23009
23010    let mut rename_handler =
23011        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23012            let edit = lsp::TextEdit {
23013                range: lsp::Range {
23014                    start: lsp::Position {
23015                        line: 0,
23016                        character: 7,
23017                    },
23018                    end: lsp::Position {
23019                        line: 0,
23020                        character: 10,
23021                    },
23022                },
23023                new_text: "FooRenamed".to_string(),
23024            };
23025            Ok(Some(lsp::WorkspaceEdit::new(
23026                // Specify the same edit twice
23027                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23028            )))
23029        });
23030    let rename_task = cx
23031        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23032        .expect("Confirm rename was not started");
23033    rename_handler.next().await.unwrap();
23034    rename_task.await.expect("Confirm rename failed");
23035    cx.run_until_parked();
23036
23037    // Despite two edits, only one is actually applied as those are identical
23038    cx.assert_editor_state(indoc! {"
23039        struct FooRenamedˇ {}
23040    "});
23041}
23042
23043#[gpui::test]
23044async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23045    init_test(cx, |_| {});
23046    // These capabilities indicate that the server does not support prepare rename.
23047    let capabilities = lsp::ServerCapabilities {
23048        rename_provider: Some(lsp::OneOf::Left(true)),
23049        ..Default::default()
23050    };
23051    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23052
23053    cx.set_state(indoc! {"
23054        struct Fˇoo {}
23055    "});
23056
23057    cx.update_editor(|editor, _window, cx| {
23058        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23059        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23060        editor.highlight_background::<DocumentHighlightRead>(
23061            &[highlight_range],
23062            |theme| theme.colors().editor_document_highlight_read_background,
23063            cx,
23064        );
23065    });
23066
23067    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23068        .expect("Prepare rename was not started")
23069        .await
23070        .expect("Prepare rename failed");
23071
23072    let mut rename_handler =
23073        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23074            let edit = lsp::TextEdit {
23075                range: lsp::Range {
23076                    start: lsp::Position {
23077                        line: 0,
23078                        character: 7,
23079                    },
23080                    end: lsp::Position {
23081                        line: 0,
23082                        character: 10,
23083                    },
23084                },
23085                new_text: "FooRenamed".to_string(),
23086            };
23087            Ok(Some(lsp::WorkspaceEdit::new(
23088                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23089            )))
23090        });
23091    let rename_task = cx
23092        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23093        .expect("Confirm rename was not started");
23094    rename_handler.next().await.unwrap();
23095    rename_task.await.expect("Confirm rename failed");
23096    cx.run_until_parked();
23097
23098    // Correct range is renamed, as `surrounding_word` is used to find it.
23099    cx.assert_editor_state(indoc! {"
23100        struct FooRenamedˇ {}
23101    "});
23102}
23103
23104#[gpui::test]
23105async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23106    init_test(cx, |_| {});
23107    let mut cx = EditorTestContext::new(cx).await;
23108
23109    let language = Arc::new(
23110        Language::new(
23111            LanguageConfig::default(),
23112            Some(tree_sitter_html::LANGUAGE.into()),
23113        )
23114        .with_brackets_query(
23115            r#"
23116            ("<" @open "/>" @close)
23117            ("</" @open ">" @close)
23118            ("<" @open ">" @close)
23119            ("\"" @open "\"" @close)
23120            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23121        "#,
23122        )
23123        .unwrap(),
23124    );
23125    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23126
23127    cx.set_state(indoc! {"
23128        <span>ˇ</span>
23129    "});
23130    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23131    cx.assert_editor_state(indoc! {"
23132        <span>
23133        ˇ
23134        </span>
23135    "});
23136
23137    cx.set_state(indoc! {"
23138        <span><span></span>ˇ</span>
23139    "});
23140    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23141    cx.assert_editor_state(indoc! {"
23142        <span><span></span>
23143        ˇ</span>
23144    "});
23145
23146    cx.set_state(indoc! {"
23147        <span>ˇ
23148        </span>
23149    "});
23150    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23151    cx.assert_editor_state(indoc! {"
23152        <span>
23153        ˇ
23154        </span>
23155    "});
23156}
23157
23158#[gpui::test(iterations = 10)]
23159async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23160    init_test(cx, |_| {});
23161
23162    let fs = FakeFs::new(cx.executor());
23163    fs.insert_tree(
23164        path!("/dir"),
23165        json!({
23166            "a.ts": "a",
23167        }),
23168    )
23169    .await;
23170
23171    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23172    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23173    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23174
23175    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23176    language_registry.add(Arc::new(Language::new(
23177        LanguageConfig {
23178            name: "TypeScript".into(),
23179            matcher: LanguageMatcher {
23180                path_suffixes: vec!["ts".to_string()],
23181                ..Default::default()
23182            },
23183            ..Default::default()
23184        },
23185        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23186    )));
23187    let mut fake_language_servers = language_registry.register_fake_lsp(
23188        "TypeScript",
23189        FakeLspAdapter {
23190            capabilities: lsp::ServerCapabilities {
23191                code_lens_provider: Some(lsp::CodeLensOptions {
23192                    resolve_provider: Some(true),
23193                }),
23194                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23195                    commands: vec!["_the/command".to_string()],
23196                    ..lsp::ExecuteCommandOptions::default()
23197                }),
23198                ..lsp::ServerCapabilities::default()
23199            },
23200            ..FakeLspAdapter::default()
23201        },
23202    );
23203
23204    let editor = workspace
23205        .update(cx, |workspace, window, cx| {
23206            workspace.open_abs_path(
23207                PathBuf::from(path!("/dir/a.ts")),
23208                OpenOptions::default(),
23209                window,
23210                cx,
23211            )
23212        })
23213        .unwrap()
23214        .await
23215        .unwrap()
23216        .downcast::<Editor>()
23217        .unwrap();
23218    cx.executor().run_until_parked();
23219
23220    let fake_server = fake_language_servers.next().await.unwrap();
23221
23222    let buffer = editor.update(cx, |editor, cx| {
23223        editor
23224            .buffer()
23225            .read(cx)
23226            .as_singleton()
23227            .expect("have opened a single file by path")
23228    });
23229
23230    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23231    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23232    drop(buffer_snapshot);
23233    let actions = cx
23234        .update_window(*workspace, |_, window, cx| {
23235            project.code_actions(&buffer, anchor..anchor, window, cx)
23236        })
23237        .unwrap();
23238
23239    fake_server
23240        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23241            Ok(Some(vec![
23242                lsp::CodeLens {
23243                    range: lsp::Range::default(),
23244                    command: Some(lsp::Command {
23245                        title: "Code lens command".to_owned(),
23246                        command: "_the/command".to_owned(),
23247                        arguments: None,
23248                    }),
23249                    data: None,
23250                },
23251                lsp::CodeLens {
23252                    range: lsp::Range::default(),
23253                    command: Some(lsp::Command {
23254                        title: "Command not in capabilities".to_owned(),
23255                        command: "not in capabilities".to_owned(),
23256                        arguments: None,
23257                    }),
23258                    data: None,
23259                },
23260                lsp::CodeLens {
23261                    range: lsp::Range {
23262                        start: lsp::Position {
23263                            line: 1,
23264                            character: 1,
23265                        },
23266                        end: lsp::Position {
23267                            line: 1,
23268                            character: 1,
23269                        },
23270                    },
23271                    command: Some(lsp::Command {
23272                        title: "Command not in range".to_owned(),
23273                        command: "_the/command".to_owned(),
23274                        arguments: None,
23275                    }),
23276                    data: None,
23277                },
23278            ]))
23279        })
23280        .next()
23281        .await;
23282
23283    let actions = actions.await.unwrap();
23284    assert_eq!(
23285        actions.len(),
23286        1,
23287        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23288    );
23289    let action = actions[0].clone();
23290    let apply = project.update(cx, |project, cx| {
23291        project.apply_code_action(buffer.clone(), action, true, cx)
23292    });
23293
23294    // Resolving the code action does not populate its edits. In absence of
23295    // edits, we must execute the given command.
23296    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23297        |mut lens, _| async move {
23298            let lens_command = lens.command.as_mut().expect("should have a command");
23299            assert_eq!(lens_command.title, "Code lens command");
23300            lens_command.arguments = Some(vec![json!("the-argument")]);
23301            Ok(lens)
23302        },
23303    );
23304
23305    // While executing the command, the language server sends the editor
23306    // a `workspaceEdit` request.
23307    fake_server
23308        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23309            let fake = fake_server.clone();
23310            move |params, _| {
23311                assert_eq!(params.command, "_the/command");
23312                let fake = fake.clone();
23313                async move {
23314                    fake.server
23315                        .request::<lsp::request::ApplyWorkspaceEdit>(
23316                            lsp::ApplyWorkspaceEditParams {
23317                                label: None,
23318                                edit: lsp::WorkspaceEdit {
23319                                    changes: Some(
23320                                        [(
23321                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23322                                            vec![lsp::TextEdit {
23323                                                range: lsp::Range::new(
23324                                                    lsp::Position::new(0, 0),
23325                                                    lsp::Position::new(0, 0),
23326                                                ),
23327                                                new_text: "X".into(),
23328                                            }],
23329                                        )]
23330                                        .into_iter()
23331                                        .collect(),
23332                                    ),
23333                                    ..lsp::WorkspaceEdit::default()
23334                                },
23335                            },
23336                        )
23337                        .await
23338                        .into_response()
23339                        .unwrap();
23340                    Ok(Some(json!(null)))
23341                }
23342            }
23343        })
23344        .next()
23345        .await;
23346
23347    // Applying the code lens command returns a project transaction containing the edits
23348    // sent by the language server in its `workspaceEdit` request.
23349    let transaction = apply.await.unwrap();
23350    assert!(transaction.0.contains_key(&buffer));
23351    buffer.update(cx, |buffer, cx| {
23352        assert_eq!(buffer.text(), "Xa");
23353        buffer.undo(cx);
23354        assert_eq!(buffer.text(), "a");
23355    });
23356
23357    let actions_after_edits = cx
23358        .update_window(*workspace, |_, window, cx| {
23359            project.code_actions(&buffer, anchor..anchor, window, cx)
23360        })
23361        .unwrap()
23362        .await
23363        .unwrap();
23364    assert_eq!(
23365        actions, actions_after_edits,
23366        "For the same selection, same code lens actions should be returned"
23367    );
23368
23369    let _responses =
23370        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23371            panic!("No more code lens requests are expected");
23372        });
23373    editor.update_in(cx, |editor, window, cx| {
23374        editor.select_all(&SelectAll, window, cx);
23375    });
23376    cx.executor().run_until_parked();
23377    let new_actions = cx
23378        .update_window(*workspace, |_, window, cx| {
23379            project.code_actions(&buffer, anchor..anchor, window, cx)
23380        })
23381        .unwrap()
23382        .await
23383        .unwrap();
23384    assert_eq!(
23385        actions, new_actions,
23386        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23387    );
23388}
23389
23390#[gpui::test]
23391async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23392    init_test(cx, |_| {});
23393
23394    let fs = FakeFs::new(cx.executor());
23395    let main_text = r#"fn main() {
23396println!("1");
23397println!("2");
23398println!("3");
23399println!("4");
23400println!("5");
23401}"#;
23402    let lib_text = "mod foo {}";
23403    fs.insert_tree(
23404        path!("/a"),
23405        json!({
23406            "lib.rs": lib_text,
23407            "main.rs": main_text,
23408        }),
23409    )
23410    .await;
23411
23412    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23413    let (workspace, cx) =
23414        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23415    let worktree_id = workspace.update(cx, |workspace, cx| {
23416        workspace.project().update(cx, |project, cx| {
23417            project.worktrees(cx).next().unwrap().read(cx).id()
23418        })
23419    });
23420
23421    let expected_ranges = vec![
23422        Point::new(0, 0)..Point::new(0, 0),
23423        Point::new(1, 0)..Point::new(1, 1),
23424        Point::new(2, 0)..Point::new(2, 2),
23425        Point::new(3, 0)..Point::new(3, 3),
23426    ];
23427
23428    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23429    let editor_1 = workspace
23430        .update_in(cx, |workspace, window, cx| {
23431            workspace.open_path(
23432                (worktree_id, "main.rs"),
23433                Some(pane_1.downgrade()),
23434                true,
23435                window,
23436                cx,
23437            )
23438        })
23439        .unwrap()
23440        .await
23441        .downcast::<Editor>()
23442        .unwrap();
23443    pane_1.update(cx, |pane, cx| {
23444        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23445        open_editor.update(cx, |editor, cx| {
23446            assert_eq!(
23447                editor.display_text(cx),
23448                main_text,
23449                "Original main.rs text on initial open",
23450            );
23451            assert_eq!(
23452                editor
23453                    .selections
23454                    .all::<Point>(cx)
23455                    .into_iter()
23456                    .map(|s| s.range())
23457                    .collect::<Vec<_>>(),
23458                vec![Point::zero()..Point::zero()],
23459                "Default selections on initial open",
23460            );
23461        })
23462    });
23463    editor_1.update_in(cx, |editor, window, cx| {
23464        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23465            s.select_ranges(expected_ranges.clone());
23466        });
23467    });
23468
23469    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23470        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23471    });
23472    let editor_2 = workspace
23473        .update_in(cx, |workspace, window, cx| {
23474            workspace.open_path(
23475                (worktree_id, "main.rs"),
23476                Some(pane_2.downgrade()),
23477                true,
23478                window,
23479                cx,
23480            )
23481        })
23482        .unwrap()
23483        .await
23484        .downcast::<Editor>()
23485        .unwrap();
23486    pane_2.update(cx, |pane, cx| {
23487        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23488        open_editor.update(cx, |editor, cx| {
23489            assert_eq!(
23490                editor.display_text(cx),
23491                main_text,
23492                "Original main.rs text on initial open in another panel",
23493            );
23494            assert_eq!(
23495                editor
23496                    .selections
23497                    .all::<Point>(cx)
23498                    .into_iter()
23499                    .map(|s| s.range())
23500                    .collect::<Vec<_>>(),
23501                vec![Point::zero()..Point::zero()],
23502                "Default selections on initial open in another panel",
23503            );
23504        })
23505    });
23506
23507    editor_2.update_in(cx, |editor, window, cx| {
23508        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23509    });
23510
23511    let _other_editor_1 = workspace
23512        .update_in(cx, |workspace, window, cx| {
23513            workspace.open_path(
23514                (worktree_id, "lib.rs"),
23515                Some(pane_1.downgrade()),
23516                true,
23517                window,
23518                cx,
23519            )
23520        })
23521        .unwrap()
23522        .await
23523        .downcast::<Editor>()
23524        .unwrap();
23525    pane_1
23526        .update_in(cx, |pane, window, cx| {
23527            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23528        })
23529        .await
23530        .unwrap();
23531    drop(editor_1);
23532    pane_1.update(cx, |pane, cx| {
23533        pane.active_item()
23534            .unwrap()
23535            .downcast::<Editor>()
23536            .unwrap()
23537            .update(cx, |editor, cx| {
23538                assert_eq!(
23539                    editor.display_text(cx),
23540                    lib_text,
23541                    "Other file should be open and active",
23542                );
23543            });
23544        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23545    });
23546
23547    let _other_editor_2 = workspace
23548        .update_in(cx, |workspace, window, cx| {
23549            workspace.open_path(
23550                (worktree_id, "lib.rs"),
23551                Some(pane_2.downgrade()),
23552                true,
23553                window,
23554                cx,
23555            )
23556        })
23557        .unwrap()
23558        .await
23559        .downcast::<Editor>()
23560        .unwrap();
23561    pane_2
23562        .update_in(cx, |pane, window, cx| {
23563            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23564        })
23565        .await
23566        .unwrap();
23567    drop(editor_2);
23568    pane_2.update(cx, |pane, cx| {
23569        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23570        open_editor.update(cx, |editor, cx| {
23571            assert_eq!(
23572                editor.display_text(cx),
23573                lib_text,
23574                "Other file should be open and active in another panel too",
23575            );
23576        });
23577        assert_eq!(
23578            pane.items().count(),
23579            1,
23580            "No other editors should be open in another pane",
23581        );
23582    });
23583
23584    let _editor_1_reopened = workspace
23585        .update_in(cx, |workspace, window, cx| {
23586            workspace.open_path(
23587                (worktree_id, "main.rs"),
23588                Some(pane_1.downgrade()),
23589                true,
23590                window,
23591                cx,
23592            )
23593        })
23594        .unwrap()
23595        .await
23596        .downcast::<Editor>()
23597        .unwrap();
23598    let _editor_2_reopened = workspace
23599        .update_in(cx, |workspace, window, cx| {
23600            workspace.open_path(
23601                (worktree_id, "main.rs"),
23602                Some(pane_2.downgrade()),
23603                true,
23604                window,
23605                cx,
23606            )
23607        })
23608        .unwrap()
23609        .await
23610        .downcast::<Editor>()
23611        .unwrap();
23612    pane_1.update(cx, |pane, cx| {
23613        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23614        open_editor.update(cx, |editor, cx| {
23615            assert_eq!(
23616                editor.display_text(cx),
23617                main_text,
23618                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23619            );
23620            assert_eq!(
23621                editor
23622                    .selections
23623                    .all::<Point>(cx)
23624                    .into_iter()
23625                    .map(|s| s.range())
23626                    .collect::<Vec<_>>(),
23627                expected_ranges,
23628                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23629            );
23630        })
23631    });
23632    pane_2.update(cx, |pane, cx| {
23633        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23634        open_editor.update(cx, |editor, cx| {
23635            assert_eq!(
23636                editor.display_text(cx),
23637                r#"fn main() {
23638⋯rintln!("1");
23639⋯intln!("2");
23640⋯ntln!("3");
23641println!("4");
23642println!("5");
23643}"#,
23644                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23645            );
23646            assert_eq!(
23647                editor
23648                    .selections
23649                    .all::<Point>(cx)
23650                    .into_iter()
23651                    .map(|s| s.range())
23652                    .collect::<Vec<_>>(),
23653                vec![Point::zero()..Point::zero()],
23654                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23655            );
23656        })
23657    });
23658}
23659
23660#[gpui::test]
23661async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23662    init_test(cx, |_| {});
23663
23664    let fs = FakeFs::new(cx.executor());
23665    let main_text = r#"fn main() {
23666println!("1");
23667println!("2");
23668println!("3");
23669println!("4");
23670println!("5");
23671}"#;
23672    let lib_text = "mod foo {}";
23673    fs.insert_tree(
23674        path!("/a"),
23675        json!({
23676            "lib.rs": lib_text,
23677            "main.rs": main_text,
23678        }),
23679    )
23680    .await;
23681
23682    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23683    let (workspace, cx) =
23684        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23685    let worktree_id = workspace.update(cx, |workspace, cx| {
23686        workspace.project().update(cx, |project, cx| {
23687            project.worktrees(cx).next().unwrap().read(cx).id()
23688        })
23689    });
23690
23691    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23692    let editor = workspace
23693        .update_in(cx, |workspace, window, cx| {
23694            workspace.open_path(
23695                (worktree_id, "main.rs"),
23696                Some(pane.downgrade()),
23697                true,
23698                window,
23699                cx,
23700            )
23701        })
23702        .unwrap()
23703        .await
23704        .downcast::<Editor>()
23705        .unwrap();
23706    pane.update(cx, |pane, cx| {
23707        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23708        open_editor.update(cx, |editor, cx| {
23709            assert_eq!(
23710                editor.display_text(cx),
23711                main_text,
23712                "Original main.rs text on initial open",
23713            );
23714        })
23715    });
23716    editor.update_in(cx, |editor, window, cx| {
23717        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23718    });
23719
23720    cx.update_global(|store: &mut SettingsStore, cx| {
23721        store.update_user_settings(cx, |s| {
23722            s.workspace.restore_on_file_reopen = Some(false);
23723        });
23724    });
23725    editor.update_in(cx, |editor, window, cx| {
23726        editor.fold_ranges(
23727            vec![
23728                Point::new(1, 0)..Point::new(1, 1),
23729                Point::new(2, 0)..Point::new(2, 2),
23730                Point::new(3, 0)..Point::new(3, 3),
23731            ],
23732            false,
23733            window,
23734            cx,
23735        );
23736    });
23737    pane.update_in(cx, |pane, window, cx| {
23738        pane.close_all_items(&CloseAllItems::default(), window, cx)
23739    })
23740    .await
23741    .unwrap();
23742    pane.update(cx, |pane, _| {
23743        assert!(pane.active_item().is_none());
23744    });
23745    cx.update_global(|store: &mut SettingsStore, cx| {
23746        store.update_user_settings(cx, |s| {
23747            s.workspace.restore_on_file_reopen = Some(true);
23748        });
23749    });
23750
23751    let _editor_reopened = workspace
23752        .update_in(cx, |workspace, window, cx| {
23753            workspace.open_path(
23754                (worktree_id, "main.rs"),
23755                Some(pane.downgrade()),
23756                true,
23757                window,
23758                cx,
23759            )
23760        })
23761        .unwrap()
23762        .await
23763        .downcast::<Editor>()
23764        .unwrap();
23765    pane.update(cx, |pane, cx| {
23766        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23767        open_editor.update(cx, |editor, cx| {
23768            assert_eq!(
23769                editor.display_text(cx),
23770                main_text,
23771                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23772            );
23773        })
23774    });
23775}
23776
23777#[gpui::test]
23778async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23779    struct EmptyModalView {
23780        focus_handle: gpui::FocusHandle,
23781    }
23782    impl EventEmitter<DismissEvent> for EmptyModalView {}
23783    impl Render for EmptyModalView {
23784        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23785            div()
23786        }
23787    }
23788    impl Focusable for EmptyModalView {
23789        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23790            self.focus_handle.clone()
23791        }
23792    }
23793    impl workspace::ModalView for EmptyModalView {}
23794    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23795        EmptyModalView {
23796            focus_handle: cx.focus_handle(),
23797        }
23798    }
23799
23800    init_test(cx, |_| {});
23801
23802    let fs = FakeFs::new(cx.executor());
23803    let project = Project::test(fs, [], cx).await;
23804    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23805    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23806    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23807    let editor = cx.new_window_entity(|window, cx| {
23808        Editor::new(
23809            EditorMode::full(),
23810            buffer,
23811            Some(project.clone()),
23812            window,
23813            cx,
23814        )
23815    });
23816    workspace
23817        .update(cx, |workspace, window, cx| {
23818            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23819        })
23820        .unwrap();
23821    editor.update_in(cx, |editor, window, cx| {
23822        editor.open_context_menu(&OpenContextMenu, window, cx);
23823        assert!(editor.mouse_context_menu.is_some());
23824    });
23825    workspace
23826        .update(cx, |workspace, window, cx| {
23827            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23828        })
23829        .unwrap();
23830    cx.read(|cx| {
23831        assert!(editor.read(cx).mouse_context_menu.is_none());
23832    });
23833}
23834
23835fn set_linked_edit_ranges(
23836    opening: (Point, Point),
23837    closing: (Point, Point),
23838    editor: &mut Editor,
23839    cx: &mut Context<Editor>,
23840) {
23841    let Some((buffer, _)) = editor
23842        .buffer
23843        .read(cx)
23844        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
23845    else {
23846        panic!("Failed to get buffer for selection position");
23847    };
23848    let buffer = buffer.read(cx);
23849    let buffer_id = buffer.remote_id();
23850    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
23851    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
23852    let mut linked_ranges = HashMap::default();
23853    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
23854    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
23855}
23856
23857#[gpui::test]
23858async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
23859    init_test(cx, |_| {});
23860
23861    let fs = FakeFs::new(cx.executor());
23862    fs.insert_file(path!("/file.html"), Default::default())
23863        .await;
23864
23865    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
23866
23867    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23868    let html_language = Arc::new(Language::new(
23869        LanguageConfig {
23870            name: "HTML".into(),
23871            matcher: LanguageMatcher {
23872                path_suffixes: vec!["html".to_string()],
23873                ..LanguageMatcher::default()
23874            },
23875            brackets: BracketPairConfig {
23876                pairs: vec![BracketPair {
23877                    start: "<".into(),
23878                    end: ">".into(),
23879                    close: true,
23880                    ..Default::default()
23881                }],
23882                ..Default::default()
23883            },
23884            ..Default::default()
23885        },
23886        Some(tree_sitter_html::LANGUAGE.into()),
23887    ));
23888    language_registry.add(html_language);
23889    let mut fake_servers = language_registry.register_fake_lsp(
23890        "HTML",
23891        FakeLspAdapter {
23892            capabilities: lsp::ServerCapabilities {
23893                completion_provider: Some(lsp::CompletionOptions {
23894                    resolve_provider: Some(true),
23895                    ..Default::default()
23896                }),
23897                ..Default::default()
23898            },
23899            ..Default::default()
23900        },
23901    );
23902
23903    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23904    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23905
23906    let worktree_id = workspace
23907        .update(cx, |workspace, _window, cx| {
23908            workspace.project().update(cx, |project, cx| {
23909                project.worktrees(cx).next().unwrap().read(cx).id()
23910            })
23911        })
23912        .unwrap();
23913    project
23914        .update(cx, |project, cx| {
23915            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
23916        })
23917        .await
23918        .unwrap();
23919    let editor = workspace
23920        .update(cx, |workspace, window, cx| {
23921            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
23922        })
23923        .unwrap()
23924        .await
23925        .unwrap()
23926        .downcast::<Editor>()
23927        .unwrap();
23928
23929    let fake_server = fake_servers.next().await.unwrap();
23930    editor.update_in(cx, |editor, window, cx| {
23931        editor.set_text("<ad></ad>", window, cx);
23932        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23933            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
23934        });
23935        set_linked_edit_ranges(
23936            (Point::new(0, 1), Point::new(0, 3)),
23937            (Point::new(0, 6), Point::new(0, 8)),
23938            editor,
23939            cx,
23940        );
23941    });
23942    let mut completion_handle =
23943        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
23944            Ok(Some(lsp::CompletionResponse::Array(vec![
23945                lsp::CompletionItem {
23946                    label: "head".to_string(),
23947                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23948                        lsp::InsertReplaceEdit {
23949                            new_text: "head".to_string(),
23950                            insert: lsp::Range::new(
23951                                lsp::Position::new(0, 1),
23952                                lsp::Position::new(0, 3),
23953                            ),
23954                            replace: lsp::Range::new(
23955                                lsp::Position::new(0, 1),
23956                                lsp::Position::new(0, 3),
23957                            ),
23958                        },
23959                    )),
23960                    ..Default::default()
23961                },
23962            ])))
23963        });
23964    editor.update_in(cx, |editor, window, cx| {
23965        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
23966    });
23967    cx.run_until_parked();
23968    completion_handle.next().await.unwrap();
23969    editor.update(cx, |editor, _| {
23970        assert!(
23971            editor.context_menu_visible(),
23972            "Completion menu should be visible"
23973        );
23974    });
23975    editor.update_in(cx, |editor, window, cx| {
23976        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
23977    });
23978    cx.executor().run_until_parked();
23979    editor.update(cx, |editor, cx| {
23980        assert_eq!(editor.text(cx), "<head></head>");
23981    });
23982}
23983
23984#[gpui::test]
23985async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
23986    init_test(cx, |_| {});
23987
23988    let mut cx = EditorTestContext::new(cx).await;
23989    let language = Arc::new(Language::new(
23990        LanguageConfig {
23991            name: "TSX".into(),
23992            matcher: LanguageMatcher {
23993                path_suffixes: vec!["tsx".to_string()],
23994                ..LanguageMatcher::default()
23995            },
23996            brackets: BracketPairConfig {
23997                pairs: vec![BracketPair {
23998                    start: "<".into(),
23999                    end: ">".into(),
24000                    close: true,
24001                    ..Default::default()
24002                }],
24003                ..Default::default()
24004            },
24005            linked_edit_characters: HashSet::from_iter(['.']),
24006            ..Default::default()
24007        },
24008        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24009    ));
24010    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24011
24012    // Test typing > does not extend linked pair
24013    cx.set_state("<divˇ<div></div>");
24014    cx.update_editor(|editor, _, cx| {
24015        set_linked_edit_ranges(
24016            (Point::new(0, 1), Point::new(0, 4)),
24017            (Point::new(0, 11), Point::new(0, 14)),
24018            editor,
24019            cx,
24020        );
24021    });
24022    cx.update_editor(|editor, window, cx| {
24023        editor.handle_input(">", window, cx);
24024    });
24025    cx.assert_editor_state("<div>ˇ<div></div>");
24026
24027    // Test typing . do extend linked pair
24028    cx.set_state("<Animatedˇ></Animated>");
24029    cx.update_editor(|editor, _, cx| {
24030        set_linked_edit_ranges(
24031            (Point::new(0, 1), Point::new(0, 9)),
24032            (Point::new(0, 12), Point::new(0, 20)),
24033            editor,
24034            cx,
24035        );
24036    });
24037    cx.update_editor(|editor, window, cx| {
24038        editor.handle_input(".", window, cx);
24039    });
24040    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24041    cx.update_editor(|editor, _, cx| {
24042        set_linked_edit_ranges(
24043            (Point::new(0, 1), Point::new(0, 10)),
24044            (Point::new(0, 13), Point::new(0, 21)),
24045            editor,
24046            cx,
24047        );
24048    });
24049    cx.update_editor(|editor, window, cx| {
24050        editor.handle_input("V", window, cx);
24051    });
24052    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24053}
24054
24055#[gpui::test]
24056async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24057    init_test(cx, |_| {});
24058
24059    let fs = FakeFs::new(cx.executor());
24060    fs.insert_tree(
24061        path!("/root"),
24062        json!({
24063            "a": {
24064                "main.rs": "fn main() {}",
24065            },
24066            "foo": {
24067                "bar": {
24068                    "external_file.rs": "pub mod external {}",
24069                }
24070            }
24071        }),
24072    )
24073    .await;
24074
24075    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24076    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24077    language_registry.add(rust_lang());
24078    let _fake_servers = language_registry.register_fake_lsp(
24079        "Rust",
24080        FakeLspAdapter {
24081            ..FakeLspAdapter::default()
24082        },
24083    );
24084    let (workspace, cx) =
24085        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24086    let worktree_id = workspace.update(cx, |workspace, cx| {
24087        workspace.project().update(cx, |project, cx| {
24088            project.worktrees(cx).next().unwrap().read(cx).id()
24089        })
24090    });
24091
24092    let assert_language_servers_count =
24093        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24094            project.update(cx, |project, cx| {
24095                let current = project
24096                    .lsp_store()
24097                    .read(cx)
24098                    .as_local()
24099                    .unwrap()
24100                    .language_servers
24101                    .len();
24102                assert_eq!(expected, current, "{context}");
24103            });
24104        };
24105
24106    assert_language_servers_count(
24107        0,
24108        "No servers should be running before any file is open",
24109        cx,
24110    );
24111    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24112    let main_editor = workspace
24113        .update_in(cx, |workspace, window, cx| {
24114            workspace.open_path(
24115                (worktree_id, "main.rs"),
24116                Some(pane.downgrade()),
24117                true,
24118                window,
24119                cx,
24120            )
24121        })
24122        .unwrap()
24123        .await
24124        .downcast::<Editor>()
24125        .unwrap();
24126    pane.update(cx, |pane, cx| {
24127        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24128        open_editor.update(cx, |editor, cx| {
24129            assert_eq!(
24130                editor.display_text(cx),
24131                "fn main() {}",
24132                "Original main.rs text on initial open",
24133            );
24134        });
24135        assert_eq!(open_editor, main_editor);
24136    });
24137    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24138
24139    let external_editor = workspace
24140        .update_in(cx, |workspace, window, cx| {
24141            workspace.open_abs_path(
24142                PathBuf::from("/root/foo/bar/external_file.rs"),
24143                OpenOptions::default(),
24144                window,
24145                cx,
24146            )
24147        })
24148        .await
24149        .expect("opening external file")
24150        .downcast::<Editor>()
24151        .expect("downcasted external file's open element to editor");
24152    pane.update(cx, |pane, cx| {
24153        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24154        open_editor.update(cx, |editor, cx| {
24155            assert_eq!(
24156                editor.display_text(cx),
24157                "pub mod external {}",
24158                "External file is open now",
24159            );
24160        });
24161        assert_eq!(open_editor, external_editor);
24162    });
24163    assert_language_servers_count(
24164        1,
24165        "Second, external, *.rs file should join the existing server",
24166        cx,
24167    );
24168
24169    pane.update_in(cx, |pane, window, cx| {
24170        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24171    })
24172    .await
24173    .unwrap();
24174    pane.update_in(cx, |pane, window, cx| {
24175        pane.navigate_backward(&Default::default(), window, cx);
24176    });
24177    cx.run_until_parked();
24178    pane.update(cx, |pane, cx| {
24179        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24180        open_editor.update(cx, |editor, cx| {
24181            assert_eq!(
24182                editor.display_text(cx),
24183                "pub mod external {}",
24184                "External file is open now",
24185            );
24186        });
24187    });
24188    assert_language_servers_count(
24189        1,
24190        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24191        cx,
24192    );
24193
24194    cx.update(|_, cx| {
24195        workspace::reload(cx);
24196    });
24197    assert_language_servers_count(
24198        1,
24199        "After reloading the worktree with local and external files opened, only one project should be started",
24200        cx,
24201    );
24202}
24203
24204#[gpui::test]
24205async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24206    init_test(cx, |_| {});
24207
24208    let mut cx = EditorTestContext::new(cx).await;
24209    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24210    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24211
24212    // test cursor move to start of each line on tab
24213    // for `if`, `elif`, `else`, `while`, `with` and `for`
24214    cx.set_state(indoc! {"
24215        def main():
24216        ˇ    for item in items:
24217        ˇ        while item.active:
24218        ˇ            if item.value > 10:
24219        ˇ                continue
24220        ˇ            elif item.value < 0:
24221        ˇ                break
24222        ˇ            else:
24223        ˇ                with item.context() as ctx:
24224        ˇ                    yield count
24225        ˇ        else:
24226        ˇ            log('while else')
24227        ˇ    else:
24228        ˇ        log('for else')
24229    "});
24230    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24231    cx.assert_editor_state(indoc! {"
24232        def main():
24233            ˇfor item in items:
24234                ˇwhile item.active:
24235                    ˇif item.value > 10:
24236                        ˇcontinue
24237                    ˇelif item.value < 0:
24238                        ˇbreak
24239                    ˇelse:
24240                        ˇwith item.context() as ctx:
24241                            ˇyield count
24242                ˇelse:
24243                    ˇlog('while else')
24244            ˇelse:
24245                ˇlog('for else')
24246    "});
24247    // test relative indent is preserved when tab
24248    // for `if`, `elif`, `else`, `while`, `with` and `for`
24249    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24250    cx.assert_editor_state(indoc! {"
24251        def main():
24252                ˇfor item in items:
24253                    ˇwhile item.active:
24254                        ˇif item.value > 10:
24255                            ˇcontinue
24256                        ˇelif item.value < 0:
24257                            ˇbreak
24258                        ˇelse:
24259                            ˇwith item.context() as ctx:
24260                                ˇyield count
24261                    ˇelse:
24262                        ˇlog('while else')
24263                ˇelse:
24264                    ˇlog('for else')
24265    "});
24266
24267    // test cursor move to start of each line on tab
24268    // for `try`, `except`, `else`, `finally`, `match` and `def`
24269    cx.set_state(indoc! {"
24270        def main():
24271        ˇ    try:
24272        ˇ        fetch()
24273        ˇ    except ValueError:
24274        ˇ        handle_error()
24275        ˇ    else:
24276        ˇ        match value:
24277        ˇ            case _:
24278        ˇ    finally:
24279        ˇ        def status():
24280        ˇ            return 0
24281    "});
24282    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24283    cx.assert_editor_state(indoc! {"
24284        def main():
24285            ˇtry:
24286                ˇfetch()
24287            ˇexcept ValueError:
24288                ˇhandle_error()
24289            ˇelse:
24290                ˇmatch value:
24291                    ˇcase _:
24292            ˇfinally:
24293                ˇdef status():
24294                    ˇreturn 0
24295    "});
24296    // test relative indent is preserved when tab
24297    // for `try`, `except`, `else`, `finally`, `match` and `def`
24298    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24299    cx.assert_editor_state(indoc! {"
24300        def main():
24301                ˇtry:
24302                    ˇfetch()
24303                ˇexcept ValueError:
24304                    ˇhandle_error()
24305                ˇelse:
24306                    ˇmatch value:
24307                        ˇcase _:
24308                ˇfinally:
24309                    ˇdef status():
24310                        ˇreturn 0
24311    "});
24312}
24313
24314#[gpui::test]
24315async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24316    init_test(cx, |_| {});
24317
24318    let mut cx = EditorTestContext::new(cx).await;
24319    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24320    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24321
24322    // test `else` auto outdents when typed inside `if` block
24323    cx.set_state(indoc! {"
24324        def main():
24325            if i == 2:
24326                return
24327                ˇ
24328    "});
24329    cx.update_editor(|editor, window, cx| {
24330        editor.handle_input("else:", window, cx);
24331    });
24332    cx.assert_editor_state(indoc! {"
24333        def main():
24334            if i == 2:
24335                return
24336            else:ˇ
24337    "});
24338
24339    // test `except` auto outdents when typed inside `try` block
24340    cx.set_state(indoc! {"
24341        def main():
24342            try:
24343                i = 2
24344                ˇ
24345    "});
24346    cx.update_editor(|editor, window, cx| {
24347        editor.handle_input("except:", window, cx);
24348    });
24349    cx.assert_editor_state(indoc! {"
24350        def main():
24351            try:
24352                i = 2
24353            except:ˇ
24354    "});
24355
24356    // test `else` auto outdents when typed inside `except` block
24357    cx.set_state(indoc! {"
24358        def main():
24359            try:
24360                i = 2
24361            except:
24362                j = 2
24363                ˇ
24364    "});
24365    cx.update_editor(|editor, window, cx| {
24366        editor.handle_input("else:", window, cx);
24367    });
24368    cx.assert_editor_state(indoc! {"
24369        def main():
24370            try:
24371                i = 2
24372            except:
24373                j = 2
24374            else:ˇ
24375    "});
24376
24377    // test `finally` auto outdents when typed inside `else` block
24378    cx.set_state(indoc! {"
24379        def main():
24380            try:
24381                i = 2
24382            except:
24383                j = 2
24384            else:
24385                k = 2
24386                ˇ
24387    "});
24388    cx.update_editor(|editor, window, cx| {
24389        editor.handle_input("finally:", window, cx);
24390    });
24391    cx.assert_editor_state(indoc! {"
24392        def main():
24393            try:
24394                i = 2
24395            except:
24396                j = 2
24397            else:
24398                k = 2
24399            finally:ˇ
24400    "});
24401
24402    // test `else` does not outdents when typed inside `except` block right after for block
24403    cx.set_state(indoc! {"
24404        def main():
24405            try:
24406                i = 2
24407            except:
24408                for i in range(n):
24409                    pass
24410                ˇ
24411    "});
24412    cx.update_editor(|editor, window, cx| {
24413        editor.handle_input("else:", window, cx);
24414    });
24415    cx.assert_editor_state(indoc! {"
24416        def main():
24417            try:
24418                i = 2
24419            except:
24420                for i in range(n):
24421                    pass
24422                else:ˇ
24423    "});
24424
24425    // test `finally` auto outdents when typed inside `else` block right after for block
24426    cx.set_state(indoc! {"
24427        def main():
24428            try:
24429                i = 2
24430            except:
24431                j = 2
24432            else:
24433                for i in range(n):
24434                    pass
24435                ˇ
24436    "});
24437    cx.update_editor(|editor, window, cx| {
24438        editor.handle_input("finally:", window, cx);
24439    });
24440    cx.assert_editor_state(indoc! {"
24441        def main():
24442            try:
24443                i = 2
24444            except:
24445                j = 2
24446            else:
24447                for i in range(n):
24448                    pass
24449            finally:ˇ
24450    "});
24451
24452    // test `except` outdents to inner "try" block
24453    cx.set_state(indoc! {"
24454        def main():
24455            try:
24456                i = 2
24457                if i == 2:
24458                    try:
24459                        i = 3
24460                        ˇ
24461    "});
24462    cx.update_editor(|editor, window, cx| {
24463        editor.handle_input("except:", window, cx);
24464    });
24465    cx.assert_editor_state(indoc! {"
24466        def main():
24467            try:
24468                i = 2
24469                if i == 2:
24470                    try:
24471                        i = 3
24472                    except:ˇ
24473    "});
24474
24475    // test `except` outdents to outer "try" block
24476    cx.set_state(indoc! {"
24477        def main():
24478            try:
24479                i = 2
24480                if i == 2:
24481                    try:
24482                        i = 3
24483                ˇ
24484    "});
24485    cx.update_editor(|editor, window, cx| {
24486        editor.handle_input("except:", window, cx);
24487    });
24488    cx.assert_editor_state(indoc! {"
24489        def main():
24490            try:
24491                i = 2
24492                if i == 2:
24493                    try:
24494                        i = 3
24495            except:ˇ
24496    "});
24497
24498    // test `else` stays at correct indent when typed after `for` block
24499    cx.set_state(indoc! {"
24500        def main():
24501            for i in range(10):
24502                if i == 3:
24503                    break
24504            ˇ
24505    "});
24506    cx.update_editor(|editor, window, cx| {
24507        editor.handle_input("else:", window, cx);
24508    });
24509    cx.assert_editor_state(indoc! {"
24510        def main():
24511            for i in range(10):
24512                if i == 3:
24513                    break
24514            else:ˇ
24515    "});
24516
24517    // test does not outdent on typing after line with square brackets
24518    cx.set_state(indoc! {"
24519        def f() -> list[str]:
24520            ˇ
24521    "});
24522    cx.update_editor(|editor, window, cx| {
24523        editor.handle_input("a", window, cx);
24524    });
24525    cx.assert_editor_state(indoc! {"
24526        def f() -> list[str]:
2452724528    "});
24529
24530    // test does not outdent on typing : after case keyword
24531    cx.set_state(indoc! {"
24532        match 1:
24533            caseˇ
24534    "});
24535    cx.update_editor(|editor, window, cx| {
24536        editor.handle_input(":", window, cx);
24537    });
24538    cx.assert_editor_state(indoc! {"
24539        match 1:
24540            case:ˇ
24541    "});
24542}
24543
24544#[gpui::test]
24545async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24546    init_test(cx, |_| {});
24547    update_test_language_settings(cx, |settings| {
24548        settings.defaults.extend_comment_on_newline = Some(false);
24549    });
24550    let mut cx = EditorTestContext::new(cx).await;
24551    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24552    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24553
24554    // test correct indent after newline on comment
24555    cx.set_state(indoc! {"
24556        # COMMENT:ˇ
24557    "});
24558    cx.update_editor(|editor, window, cx| {
24559        editor.newline(&Newline, window, cx);
24560    });
24561    cx.assert_editor_state(indoc! {"
24562        # COMMENT:
24563        ˇ
24564    "});
24565
24566    // test correct indent after newline in brackets
24567    cx.set_state(indoc! {"
24568        {ˇ}
24569    "});
24570    cx.update_editor(|editor, window, cx| {
24571        editor.newline(&Newline, window, cx);
24572    });
24573    cx.run_until_parked();
24574    cx.assert_editor_state(indoc! {"
24575        {
24576            ˇ
24577        }
24578    "});
24579
24580    cx.set_state(indoc! {"
24581        (ˇ)
24582    "});
24583    cx.update_editor(|editor, window, cx| {
24584        editor.newline(&Newline, window, cx);
24585    });
24586    cx.run_until_parked();
24587    cx.assert_editor_state(indoc! {"
24588        (
24589            ˇ
24590        )
24591    "});
24592
24593    // do not indent after empty lists or dictionaries
24594    cx.set_state(indoc! {"
24595        a = []ˇ
24596    "});
24597    cx.update_editor(|editor, window, cx| {
24598        editor.newline(&Newline, window, cx);
24599    });
24600    cx.run_until_parked();
24601    cx.assert_editor_state(indoc! {"
24602        a = []
24603        ˇ
24604    "});
24605}
24606
24607#[gpui::test]
24608async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24609    init_test(cx, |_| {});
24610
24611    let mut cx = EditorTestContext::new(cx).await;
24612    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24613    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24614
24615    // test cursor move to start of each line on tab
24616    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24617    cx.set_state(indoc! {"
24618        function main() {
24619        ˇ    for item in $items; do
24620        ˇ        while [ -n \"$item\" ]; do
24621        ˇ            if [ \"$value\" -gt 10 ]; then
24622        ˇ                continue
24623        ˇ            elif [ \"$value\" -lt 0 ]; then
24624        ˇ                break
24625        ˇ            else
24626        ˇ                echo \"$item\"
24627        ˇ            fi
24628        ˇ        done
24629        ˇ    done
24630        ˇ}
24631    "});
24632    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24633    cx.assert_editor_state(indoc! {"
24634        function main() {
24635            ˇfor item in $items; do
24636                ˇwhile [ -n \"$item\" ]; do
24637                    ˇif [ \"$value\" -gt 10 ]; then
24638                        ˇcontinue
24639                    ˇelif [ \"$value\" -lt 0 ]; then
24640                        ˇbreak
24641                    ˇelse
24642                        ˇecho \"$item\"
24643                    ˇfi
24644                ˇdone
24645            ˇdone
24646        ˇ}
24647    "});
24648    // test relative indent is preserved when tab
24649    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24650    cx.assert_editor_state(indoc! {"
24651        function main() {
24652                ˇfor item in $items; do
24653                    ˇwhile [ -n \"$item\" ]; do
24654                        ˇif [ \"$value\" -gt 10 ]; then
24655                            ˇcontinue
24656                        ˇelif [ \"$value\" -lt 0 ]; then
24657                            ˇbreak
24658                        ˇelse
24659                            ˇecho \"$item\"
24660                        ˇfi
24661                    ˇdone
24662                ˇdone
24663            ˇ}
24664    "});
24665
24666    // test cursor move to start of each line on tab
24667    // for `case` statement with patterns
24668    cx.set_state(indoc! {"
24669        function handle() {
24670        ˇ    case \"$1\" in
24671        ˇ        start)
24672        ˇ            echo \"a\"
24673        ˇ            ;;
24674        ˇ        stop)
24675        ˇ            echo \"b\"
24676        ˇ            ;;
24677        ˇ        *)
24678        ˇ            echo \"c\"
24679        ˇ            ;;
24680        ˇ    esac
24681        ˇ}
24682    "});
24683    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24684    cx.assert_editor_state(indoc! {"
24685        function handle() {
24686            ˇcase \"$1\" in
24687                ˇstart)
24688                    ˇecho \"a\"
24689                    ˇ;;
24690                ˇstop)
24691                    ˇecho \"b\"
24692                    ˇ;;
24693                ˇ*)
24694                    ˇecho \"c\"
24695                    ˇ;;
24696            ˇesac
24697        ˇ}
24698    "});
24699}
24700
24701#[gpui::test]
24702async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24703    init_test(cx, |_| {});
24704
24705    let mut cx = EditorTestContext::new(cx).await;
24706    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24707    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24708
24709    // test indents on comment insert
24710    cx.set_state(indoc! {"
24711        function main() {
24712        ˇ    for item in $items; do
24713        ˇ        while [ -n \"$item\" ]; do
24714        ˇ            if [ \"$value\" -gt 10 ]; then
24715        ˇ                continue
24716        ˇ            elif [ \"$value\" -lt 0 ]; then
24717        ˇ                break
24718        ˇ            else
24719        ˇ                echo \"$item\"
24720        ˇ            fi
24721        ˇ        done
24722        ˇ    done
24723        ˇ}
24724    "});
24725    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24726    cx.assert_editor_state(indoc! {"
24727        function main() {
24728        #ˇ    for item in $items; do
24729        #ˇ        while [ -n \"$item\" ]; do
24730        #ˇ            if [ \"$value\" -gt 10 ]; then
24731        #ˇ                continue
24732        #ˇ            elif [ \"$value\" -lt 0 ]; then
24733        #ˇ                break
24734        #ˇ            else
24735        #ˇ                echo \"$item\"
24736        #ˇ            fi
24737        #ˇ        done
24738        #ˇ    done
24739        #ˇ}
24740    "});
24741}
24742
24743#[gpui::test]
24744async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24745    init_test(cx, |_| {});
24746
24747    let mut cx = EditorTestContext::new(cx).await;
24748    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24749    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24750
24751    // test `else` auto outdents when typed inside `if` block
24752    cx.set_state(indoc! {"
24753        if [ \"$1\" = \"test\" ]; then
24754            echo \"foo bar\"
24755            ˇ
24756    "});
24757    cx.update_editor(|editor, window, cx| {
24758        editor.handle_input("else", window, cx);
24759    });
24760    cx.assert_editor_state(indoc! {"
24761        if [ \"$1\" = \"test\" ]; then
24762            echo \"foo bar\"
24763        elseˇ
24764    "});
24765
24766    // test `elif` auto outdents when typed inside `if` block
24767    cx.set_state(indoc! {"
24768        if [ \"$1\" = \"test\" ]; then
24769            echo \"foo bar\"
24770            ˇ
24771    "});
24772    cx.update_editor(|editor, window, cx| {
24773        editor.handle_input("elif", window, cx);
24774    });
24775    cx.assert_editor_state(indoc! {"
24776        if [ \"$1\" = \"test\" ]; then
24777            echo \"foo bar\"
24778        elifˇ
24779    "});
24780
24781    // test `fi` auto outdents when typed inside `else` block
24782    cx.set_state(indoc! {"
24783        if [ \"$1\" = \"test\" ]; then
24784            echo \"foo bar\"
24785        else
24786            echo \"bar baz\"
24787            ˇ
24788    "});
24789    cx.update_editor(|editor, window, cx| {
24790        editor.handle_input("fi", window, cx);
24791    });
24792    cx.assert_editor_state(indoc! {"
24793        if [ \"$1\" = \"test\" ]; then
24794            echo \"foo bar\"
24795        else
24796            echo \"bar baz\"
24797        fiˇ
24798    "});
24799
24800    // test `done` auto outdents when typed inside `while` block
24801    cx.set_state(indoc! {"
24802        while read line; do
24803            echo \"$line\"
24804            ˇ
24805    "});
24806    cx.update_editor(|editor, window, cx| {
24807        editor.handle_input("done", window, cx);
24808    });
24809    cx.assert_editor_state(indoc! {"
24810        while read line; do
24811            echo \"$line\"
24812        doneˇ
24813    "});
24814
24815    // test `done` auto outdents when typed inside `for` block
24816    cx.set_state(indoc! {"
24817        for file in *.txt; do
24818            cat \"$file\"
24819            ˇ
24820    "});
24821    cx.update_editor(|editor, window, cx| {
24822        editor.handle_input("done", window, cx);
24823    });
24824    cx.assert_editor_state(indoc! {"
24825        for file in *.txt; do
24826            cat \"$file\"
24827        doneˇ
24828    "});
24829
24830    // test `esac` auto outdents when typed inside `case` block
24831    cx.set_state(indoc! {"
24832        case \"$1\" in
24833            start)
24834                echo \"foo bar\"
24835                ;;
24836            stop)
24837                echo \"bar baz\"
24838                ;;
24839            ˇ
24840    "});
24841    cx.update_editor(|editor, window, cx| {
24842        editor.handle_input("esac", window, cx);
24843    });
24844    cx.assert_editor_state(indoc! {"
24845        case \"$1\" in
24846            start)
24847                echo \"foo bar\"
24848                ;;
24849            stop)
24850                echo \"bar baz\"
24851                ;;
24852        esacˇ
24853    "});
24854
24855    // test `*)` auto outdents when typed inside `case` block
24856    cx.set_state(indoc! {"
24857        case \"$1\" in
24858            start)
24859                echo \"foo bar\"
24860                ;;
24861                ˇ
24862    "});
24863    cx.update_editor(|editor, window, cx| {
24864        editor.handle_input("*)", window, cx);
24865    });
24866    cx.assert_editor_state(indoc! {"
24867        case \"$1\" in
24868            start)
24869                echo \"foo bar\"
24870                ;;
24871            *)ˇ
24872    "});
24873
24874    // test `fi` outdents to correct level with nested if blocks
24875    cx.set_state(indoc! {"
24876        if [ \"$1\" = \"test\" ]; then
24877            echo \"outer if\"
24878            if [ \"$2\" = \"debug\" ]; then
24879                echo \"inner if\"
24880                ˇ
24881    "});
24882    cx.update_editor(|editor, window, cx| {
24883        editor.handle_input("fi", window, cx);
24884    });
24885    cx.assert_editor_state(indoc! {"
24886        if [ \"$1\" = \"test\" ]; then
24887            echo \"outer if\"
24888            if [ \"$2\" = \"debug\" ]; then
24889                echo \"inner if\"
24890            fiˇ
24891    "});
24892}
24893
24894#[gpui::test]
24895async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
24896    init_test(cx, |_| {});
24897    update_test_language_settings(cx, |settings| {
24898        settings.defaults.extend_comment_on_newline = Some(false);
24899    });
24900    let mut cx = EditorTestContext::new(cx).await;
24901    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24902    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24903
24904    // test correct indent after newline on comment
24905    cx.set_state(indoc! {"
24906        # COMMENT:ˇ
24907    "});
24908    cx.update_editor(|editor, window, cx| {
24909        editor.newline(&Newline, window, cx);
24910    });
24911    cx.assert_editor_state(indoc! {"
24912        # COMMENT:
24913        ˇ
24914    "});
24915
24916    // test correct indent after newline after `then`
24917    cx.set_state(indoc! {"
24918
24919        if [ \"$1\" = \"test\" ]; thenˇ
24920    "});
24921    cx.update_editor(|editor, window, cx| {
24922        editor.newline(&Newline, window, cx);
24923    });
24924    cx.run_until_parked();
24925    cx.assert_editor_state(indoc! {"
24926
24927        if [ \"$1\" = \"test\" ]; then
24928            ˇ
24929    "});
24930
24931    // test correct indent after newline after `else`
24932    cx.set_state(indoc! {"
24933        if [ \"$1\" = \"test\" ]; then
24934        elseˇ
24935    "});
24936    cx.update_editor(|editor, window, cx| {
24937        editor.newline(&Newline, window, cx);
24938    });
24939    cx.run_until_parked();
24940    cx.assert_editor_state(indoc! {"
24941        if [ \"$1\" = \"test\" ]; then
24942        else
24943            ˇ
24944    "});
24945
24946    // test correct indent after newline after `elif`
24947    cx.set_state(indoc! {"
24948        if [ \"$1\" = \"test\" ]; then
24949        elifˇ
24950    "});
24951    cx.update_editor(|editor, window, cx| {
24952        editor.newline(&Newline, window, cx);
24953    });
24954    cx.run_until_parked();
24955    cx.assert_editor_state(indoc! {"
24956        if [ \"$1\" = \"test\" ]; then
24957        elif
24958            ˇ
24959    "});
24960
24961    // test correct indent after newline after `do`
24962    cx.set_state(indoc! {"
24963        for file in *.txt; doˇ
24964    "});
24965    cx.update_editor(|editor, window, cx| {
24966        editor.newline(&Newline, window, cx);
24967    });
24968    cx.run_until_parked();
24969    cx.assert_editor_state(indoc! {"
24970        for file in *.txt; do
24971            ˇ
24972    "});
24973
24974    // test correct indent after newline after case pattern
24975    cx.set_state(indoc! {"
24976        case \"$1\" in
24977            start)ˇ
24978    "});
24979    cx.update_editor(|editor, window, cx| {
24980        editor.newline(&Newline, window, cx);
24981    });
24982    cx.run_until_parked();
24983    cx.assert_editor_state(indoc! {"
24984        case \"$1\" in
24985            start)
24986                ˇ
24987    "});
24988
24989    // test correct indent after newline after case pattern
24990    cx.set_state(indoc! {"
24991        case \"$1\" in
24992            start)
24993                ;;
24994            *)ˇ
24995    "});
24996    cx.update_editor(|editor, window, cx| {
24997        editor.newline(&Newline, window, cx);
24998    });
24999    cx.run_until_parked();
25000    cx.assert_editor_state(indoc! {"
25001        case \"$1\" in
25002            start)
25003                ;;
25004            *)
25005                ˇ
25006    "});
25007
25008    // test correct indent after newline after function opening brace
25009    cx.set_state(indoc! {"
25010        function test() {ˇ}
25011    "});
25012    cx.update_editor(|editor, window, cx| {
25013        editor.newline(&Newline, window, cx);
25014    });
25015    cx.run_until_parked();
25016    cx.assert_editor_state(indoc! {"
25017        function test() {
25018            ˇ
25019        }
25020    "});
25021
25022    // test no extra indent after semicolon on same line
25023    cx.set_state(indoc! {"
25024        echo \"test\"25025    "});
25026    cx.update_editor(|editor, window, cx| {
25027        editor.newline(&Newline, window, cx);
25028    });
25029    cx.run_until_parked();
25030    cx.assert_editor_state(indoc! {"
25031        echo \"test\";
25032        ˇ
25033    "});
25034}
25035
25036fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25037    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25038    point..point
25039}
25040
25041#[track_caller]
25042fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25043    let (text, ranges) = marked_text_ranges(marked_text, true);
25044    assert_eq!(editor.text(cx), text);
25045    assert_eq!(
25046        editor.selections.ranges(cx),
25047        ranges,
25048        "Assert selections are {}",
25049        marked_text
25050    );
25051}
25052
25053pub fn handle_signature_help_request(
25054    cx: &mut EditorLspTestContext,
25055    mocked_response: lsp::SignatureHelp,
25056) -> impl Future<Output = ()> + use<> {
25057    let mut request =
25058        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25059            let mocked_response = mocked_response.clone();
25060            async move { Ok(Some(mocked_response)) }
25061        });
25062
25063    async move {
25064        request.next().await;
25065    }
25066}
25067
25068#[track_caller]
25069pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25070    cx.update_editor(|editor, _, _| {
25071        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25072            let entries = menu.entries.borrow();
25073            let entries = entries
25074                .iter()
25075                .map(|entry| entry.string.as_str())
25076                .collect::<Vec<_>>();
25077            assert_eq!(entries, expected);
25078        } else {
25079            panic!("Expected completions menu");
25080        }
25081    });
25082}
25083
25084/// Handle completion request passing a marked string specifying where the completion
25085/// should be triggered from using '|' character, what range should be replaced, and what completions
25086/// should be returned using '<' and '>' to delimit the range.
25087///
25088/// Also see `handle_completion_request_with_insert_and_replace`.
25089#[track_caller]
25090pub fn handle_completion_request(
25091    marked_string: &str,
25092    completions: Vec<&'static str>,
25093    is_incomplete: bool,
25094    counter: Arc<AtomicUsize>,
25095    cx: &mut EditorLspTestContext,
25096) -> impl Future<Output = ()> {
25097    let complete_from_marker: TextRangeMarker = '|'.into();
25098    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25099    let (_, mut marked_ranges) = marked_text_ranges_by(
25100        marked_string,
25101        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25102    );
25103
25104    let complete_from_position =
25105        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25106    let replace_range =
25107        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25108
25109    let mut request =
25110        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25111            let completions = completions.clone();
25112            counter.fetch_add(1, atomic::Ordering::Release);
25113            async move {
25114                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25115                assert_eq!(
25116                    params.text_document_position.position,
25117                    complete_from_position
25118                );
25119                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25120                    is_incomplete,
25121                    item_defaults: None,
25122                    items: completions
25123                        .iter()
25124                        .map(|completion_text| lsp::CompletionItem {
25125                            label: completion_text.to_string(),
25126                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25127                                range: replace_range,
25128                                new_text: completion_text.to_string(),
25129                            })),
25130                            ..Default::default()
25131                        })
25132                        .collect(),
25133                })))
25134            }
25135        });
25136
25137    async move {
25138        request.next().await;
25139    }
25140}
25141
25142/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25143/// given instead, which also contains an `insert` range.
25144///
25145/// This function uses markers to define ranges:
25146/// - `|` marks the cursor position
25147/// - `<>` marks the replace range
25148/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25149pub fn handle_completion_request_with_insert_and_replace(
25150    cx: &mut EditorLspTestContext,
25151    marked_string: &str,
25152    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25153    counter: Arc<AtomicUsize>,
25154) -> impl Future<Output = ()> {
25155    let complete_from_marker: TextRangeMarker = '|'.into();
25156    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25157    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25158
25159    let (_, mut marked_ranges) = marked_text_ranges_by(
25160        marked_string,
25161        vec![
25162            complete_from_marker.clone(),
25163            replace_range_marker.clone(),
25164            insert_range_marker.clone(),
25165        ],
25166    );
25167
25168    let complete_from_position =
25169        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25170    let replace_range =
25171        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25172
25173    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25174        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25175        _ => lsp::Range {
25176            start: replace_range.start,
25177            end: complete_from_position,
25178        },
25179    };
25180
25181    let mut request =
25182        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25183            let completions = completions.clone();
25184            counter.fetch_add(1, atomic::Ordering::Release);
25185            async move {
25186                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25187                assert_eq!(
25188                    params.text_document_position.position, complete_from_position,
25189                    "marker `|` position doesn't match",
25190                );
25191                Ok(Some(lsp::CompletionResponse::Array(
25192                    completions
25193                        .iter()
25194                        .map(|(label, new_text)| lsp::CompletionItem {
25195                            label: label.to_string(),
25196                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25197                                lsp::InsertReplaceEdit {
25198                                    insert: insert_range,
25199                                    replace: replace_range,
25200                                    new_text: new_text.to_string(),
25201                                },
25202                            )),
25203                            ..Default::default()
25204                        })
25205                        .collect(),
25206                )))
25207            }
25208        });
25209
25210    async move {
25211        request.next().await;
25212    }
25213}
25214
25215fn handle_resolve_completion_request(
25216    cx: &mut EditorLspTestContext,
25217    edits: Option<Vec<(&'static str, &'static str)>>,
25218) -> impl Future<Output = ()> {
25219    let edits = edits.map(|edits| {
25220        edits
25221            .iter()
25222            .map(|(marked_string, new_text)| {
25223                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25224                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25225                lsp::TextEdit::new(replace_range, new_text.to_string())
25226            })
25227            .collect::<Vec<_>>()
25228    });
25229
25230    let mut request =
25231        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25232            let edits = edits.clone();
25233            async move {
25234                Ok(lsp::CompletionItem {
25235                    additional_text_edits: edits,
25236                    ..Default::default()
25237                })
25238            }
25239        });
25240
25241    async move {
25242        request.next().await;
25243    }
25244}
25245
25246pub(crate) fn update_test_language_settings(
25247    cx: &mut TestAppContext,
25248    f: impl Fn(&mut AllLanguageSettingsContent),
25249) {
25250    cx.update(|cx| {
25251        SettingsStore::update_global(cx, |store, cx| {
25252            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25253        });
25254    });
25255}
25256
25257pub(crate) fn update_test_project_settings(
25258    cx: &mut TestAppContext,
25259    f: impl Fn(&mut ProjectSettingsContent),
25260) {
25261    cx.update(|cx| {
25262        SettingsStore::update_global(cx, |store, cx| {
25263            store.update_user_settings(cx, |settings| f(&mut settings.project));
25264        });
25265    });
25266}
25267
25268pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25269    cx.update(|cx| {
25270        assets::Assets.load_test_fonts(cx);
25271        let store = SettingsStore::test(cx);
25272        cx.set_global(store);
25273        theme::init(theme::LoadThemes::JustBase, cx);
25274        release_channel::init(SemanticVersion::default(), cx);
25275        client::init_settings(cx);
25276        language::init(cx);
25277        Project::init_settings(cx);
25278        workspace::init_settings(cx);
25279        crate::init(cx);
25280    });
25281    zlog::init_test();
25282    update_test_language_settings(cx, f);
25283}
25284
25285#[track_caller]
25286fn assert_hunk_revert(
25287    not_reverted_text_with_selections: &str,
25288    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25289    expected_reverted_text_with_selections: &str,
25290    base_text: &str,
25291    cx: &mut EditorLspTestContext,
25292) {
25293    cx.set_state(not_reverted_text_with_selections);
25294    cx.set_head_text(base_text);
25295    cx.executor().run_until_parked();
25296
25297    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25298        let snapshot = editor.snapshot(window, cx);
25299        let reverted_hunk_statuses = snapshot
25300            .buffer_snapshot
25301            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
25302            .map(|hunk| hunk.status().kind)
25303            .collect::<Vec<_>>();
25304
25305        editor.git_restore(&Default::default(), window, cx);
25306        reverted_hunk_statuses
25307    });
25308    cx.executor().run_until_parked();
25309    cx.assert_editor_state(expected_reverted_text_with_selections);
25310    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25311}
25312
25313#[gpui::test(iterations = 10)]
25314async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25315    init_test(cx, |_| {});
25316
25317    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25318    let counter = diagnostic_requests.clone();
25319
25320    let fs = FakeFs::new(cx.executor());
25321    fs.insert_tree(
25322        path!("/a"),
25323        json!({
25324            "first.rs": "fn main() { let a = 5; }",
25325            "second.rs": "// Test file",
25326        }),
25327    )
25328    .await;
25329
25330    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25331    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25332    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25333
25334    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25335    language_registry.add(rust_lang());
25336    let mut fake_servers = language_registry.register_fake_lsp(
25337        "Rust",
25338        FakeLspAdapter {
25339            capabilities: lsp::ServerCapabilities {
25340                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25341                    lsp::DiagnosticOptions {
25342                        identifier: None,
25343                        inter_file_dependencies: true,
25344                        workspace_diagnostics: true,
25345                        work_done_progress_options: Default::default(),
25346                    },
25347                )),
25348                ..Default::default()
25349            },
25350            ..Default::default()
25351        },
25352    );
25353
25354    let editor = workspace
25355        .update(cx, |workspace, window, cx| {
25356            workspace.open_abs_path(
25357                PathBuf::from(path!("/a/first.rs")),
25358                OpenOptions::default(),
25359                window,
25360                cx,
25361            )
25362        })
25363        .unwrap()
25364        .await
25365        .unwrap()
25366        .downcast::<Editor>()
25367        .unwrap();
25368    let fake_server = fake_servers.next().await.unwrap();
25369    let server_id = fake_server.server.server_id();
25370    let mut first_request = fake_server
25371        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25372            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25373            let result_id = Some(new_result_id.to_string());
25374            assert_eq!(
25375                params.text_document.uri,
25376                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25377            );
25378            async move {
25379                Ok(lsp::DocumentDiagnosticReportResult::Report(
25380                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25381                        related_documents: None,
25382                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25383                            items: Vec::new(),
25384                            result_id,
25385                        },
25386                    }),
25387                ))
25388            }
25389        });
25390
25391    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25392        project.update(cx, |project, cx| {
25393            let buffer_id = editor
25394                .read(cx)
25395                .buffer()
25396                .read(cx)
25397                .as_singleton()
25398                .expect("created a singleton buffer")
25399                .read(cx)
25400                .remote_id();
25401            let buffer_result_id = project
25402                .lsp_store()
25403                .read(cx)
25404                .result_id(server_id, buffer_id, cx);
25405            assert_eq!(expected, buffer_result_id);
25406        });
25407    };
25408
25409    ensure_result_id(None, cx);
25410    cx.executor().advance_clock(Duration::from_millis(60));
25411    cx.executor().run_until_parked();
25412    assert_eq!(
25413        diagnostic_requests.load(atomic::Ordering::Acquire),
25414        1,
25415        "Opening file should trigger diagnostic request"
25416    );
25417    first_request
25418        .next()
25419        .await
25420        .expect("should have sent the first diagnostics pull request");
25421    ensure_result_id(Some("1".to_string()), cx);
25422
25423    // Editing should trigger diagnostics
25424    editor.update_in(cx, |editor, window, cx| {
25425        editor.handle_input("2", window, cx)
25426    });
25427    cx.executor().advance_clock(Duration::from_millis(60));
25428    cx.executor().run_until_parked();
25429    assert_eq!(
25430        diagnostic_requests.load(atomic::Ordering::Acquire),
25431        2,
25432        "Editing should trigger diagnostic request"
25433    );
25434    ensure_result_id(Some("2".to_string()), cx);
25435
25436    // Moving cursor should not trigger diagnostic request
25437    editor.update_in(cx, |editor, window, cx| {
25438        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25439            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25440        });
25441    });
25442    cx.executor().advance_clock(Duration::from_millis(60));
25443    cx.executor().run_until_parked();
25444    assert_eq!(
25445        diagnostic_requests.load(atomic::Ordering::Acquire),
25446        2,
25447        "Cursor movement should not trigger diagnostic request"
25448    );
25449    ensure_result_id(Some("2".to_string()), cx);
25450    // Multiple rapid edits should be debounced
25451    for _ in 0..5 {
25452        editor.update_in(cx, |editor, window, cx| {
25453            editor.handle_input("x", window, cx)
25454        });
25455    }
25456    cx.executor().advance_clock(Duration::from_millis(60));
25457    cx.executor().run_until_parked();
25458
25459    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25460    assert!(
25461        final_requests <= 4,
25462        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25463    );
25464    ensure_result_id(Some(final_requests.to_string()), cx);
25465}
25466
25467#[gpui::test]
25468async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25469    // Regression test for issue #11671
25470    // Previously, adding a cursor after moving multiple cursors would reset
25471    // the cursor count instead of adding to the existing cursors.
25472    init_test(cx, |_| {});
25473    let mut cx = EditorTestContext::new(cx).await;
25474
25475    // Create a simple buffer with cursor at start
25476    cx.set_state(indoc! {"
25477        ˇaaaa
25478        bbbb
25479        cccc
25480        dddd
25481        eeee
25482        ffff
25483        gggg
25484        hhhh"});
25485
25486    // Add 2 cursors below (so we have 3 total)
25487    cx.update_editor(|editor, window, cx| {
25488        editor.add_selection_below(&Default::default(), window, cx);
25489        editor.add_selection_below(&Default::default(), window, cx);
25490    });
25491
25492    // Verify we have 3 cursors
25493    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25494    assert_eq!(
25495        initial_count, 3,
25496        "Should have 3 cursors after adding 2 below"
25497    );
25498
25499    // Move down one line
25500    cx.update_editor(|editor, window, cx| {
25501        editor.move_down(&MoveDown, window, cx);
25502    });
25503
25504    // Add another cursor below
25505    cx.update_editor(|editor, window, cx| {
25506        editor.add_selection_below(&Default::default(), window, cx);
25507    });
25508
25509    // Should now have 4 cursors (3 original + 1 new)
25510    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25511    assert_eq!(
25512        final_count, 4,
25513        "Should have 4 cursors after moving and adding another"
25514    );
25515}
25516
25517#[gpui::test(iterations = 10)]
25518async fn test_document_colors(cx: &mut TestAppContext) {
25519    let expected_color = Rgba {
25520        r: 0.33,
25521        g: 0.33,
25522        b: 0.33,
25523        a: 0.33,
25524    };
25525
25526    init_test(cx, |_| {});
25527
25528    let fs = FakeFs::new(cx.executor());
25529    fs.insert_tree(
25530        path!("/a"),
25531        json!({
25532            "first.rs": "fn main() { let a = 5; }",
25533        }),
25534    )
25535    .await;
25536
25537    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25538    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25539    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25540
25541    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25542    language_registry.add(rust_lang());
25543    let mut fake_servers = language_registry.register_fake_lsp(
25544        "Rust",
25545        FakeLspAdapter {
25546            capabilities: lsp::ServerCapabilities {
25547                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25548                ..lsp::ServerCapabilities::default()
25549            },
25550            name: "rust-analyzer",
25551            ..FakeLspAdapter::default()
25552        },
25553    );
25554    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25555        "Rust",
25556        FakeLspAdapter {
25557            capabilities: lsp::ServerCapabilities {
25558                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25559                ..lsp::ServerCapabilities::default()
25560            },
25561            name: "not-rust-analyzer",
25562            ..FakeLspAdapter::default()
25563        },
25564    );
25565
25566    let editor = workspace
25567        .update(cx, |workspace, window, cx| {
25568            workspace.open_abs_path(
25569                PathBuf::from(path!("/a/first.rs")),
25570                OpenOptions::default(),
25571                window,
25572                cx,
25573            )
25574        })
25575        .unwrap()
25576        .await
25577        .unwrap()
25578        .downcast::<Editor>()
25579        .unwrap();
25580    let fake_language_server = fake_servers.next().await.unwrap();
25581    let fake_language_server_without_capabilities =
25582        fake_servers_without_capabilities.next().await.unwrap();
25583    let requests_made = Arc::new(AtomicUsize::new(0));
25584    let closure_requests_made = Arc::clone(&requests_made);
25585    let mut color_request_handle = fake_language_server
25586        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25587            let requests_made = Arc::clone(&closure_requests_made);
25588            async move {
25589                assert_eq!(
25590                    params.text_document.uri,
25591                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25592                );
25593                requests_made.fetch_add(1, atomic::Ordering::Release);
25594                Ok(vec![
25595                    lsp::ColorInformation {
25596                        range: lsp::Range {
25597                            start: lsp::Position {
25598                                line: 0,
25599                                character: 0,
25600                            },
25601                            end: lsp::Position {
25602                                line: 0,
25603                                character: 1,
25604                            },
25605                        },
25606                        color: lsp::Color {
25607                            red: 0.33,
25608                            green: 0.33,
25609                            blue: 0.33,
25610                            alpha: 0.33,
25611                        },
25612                    },
25613                    lsp::ColorInformation {
25614                        range: lsp::Range {
25615                            start: lsp::Position {
25616                                line: 0,
25617                                character: 0,
25618                            },
25619                            end: lsp::Position {
25620                                line: 0,
25621                                character: 1,
25622                            },
25623                        },
25624                        color: lsp::Color {
25625                            red: 0.33,
25626                            green: 0.33,
25627                            blue: 0.33,
25628                            alpha: 0.33,
25629                        },
25630                    },
25631                ])
25632            }
25633        });
25634
25635    let _handle = fake_language_server_without_capabilities
25636        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25637            panic!("Should not be called");
25638        });
25639    cx.executor().advance_clock(Duration::from_millis(100));
25640    color_request_handle.next().await.unwrap();
25641    cx.run_until_parked();
25642    assert_eq!(
25643        1,
25644        requests_made.load(atomic::Ordering::Acquire),
25645        "Should query for colors once per editor open"
25646    );
25647    editor.update_in(cx, |editor, _, cx| {
25648        assert_eq!(
25649            vec![expected_color],
25650            extract_color_inlays(editor, cx),
25651            "Should have an initial inlay"
25652        );
25653    });
25654
25655    // opening another file in a split should not influence the LSP query counter
25656    workspace
25657        .update(cx, |workspace, window, cx| {
25658            assert_eq!(
25659                workspace.panes().len(),
25660                1,
25661                "Should have one pane with one editor"
25662            );
25663            workspace.move_item_to_pane_in_direction(
25664                &MoveItemToPaneInDirection {
25665                    direction: SplitDirection::Right,
25666                    focus: false,
25667                    clone: true,
25668                },
25669                window,
25670                cx,
25671            );
25672        })
25673        .unwrap();
25674    cx.run_until_parked();
25675    workspace
25676        .update(cx, |workspace, _, cx| {
25677            let panes = workspace.panes();
25678            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25679            for pane in panes {
25680                let editor = pane
25681                    .read(cx)
25682                    .active_item()
25683                    .and_then(|item| item.downcast::<Editor>())
25684                    .expect("Should have opened an editor in each split");
25685                let editor_file = editor
25686                    .read(cx)
25687                    .buffer()
25688                    .read(cx)
25689                    .as_singleton()
25690                    .expect("test deals with singleton buffers")
25691                    .read(cx)
25692                    .file()
25693                    .expect("test buffese should have a file")
25694                    .path();
25695                assert_eq!(
25696                    editor_file.as_ref(),
25697                    Path::new("first.rs"),
25698                    "Both editors should be opened for the same file"
25699                )
25700            }
25701        })
25702        .unwrap();
25703
25704    cx.executor().advance_clock(Duration::from_millis(500));
25705    let save = editor.update_in(cx, |editor, window, cx| {
25706        editor.move_to_end(&MoveToEnd, window, cx);
25707        editor.handle_input("dirty", window, cx);
25708        editor.save(
25709            SaveOptions {
25710                format: true,
25711                autosave: true,
25712            },
25713            project.clone(),
25714            window,
25715            cx,
25716        )
25717    });
25718    save.await.unwrap();
25719
25720    color_request_handle.next().await.unwrap();
25721    cx.run_until_parked();
25722    assert_eq!(
25723        3,
25724        requests_made.load(atomic::Ordering::Acquire),
25725        "Should query for colors once per save and once per formatting after save"
25726    );
25727
25728    drop(editor);
25729    let close = workspace
25730        .update(cx, |workspace, window, cx| {
25731            workspace.active_pane().update(cx, |pane, cx| {
25732                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25733            })
25734        })
25735        .unwrap();
25736    close.await.unwrap();
25737    let close = workspace
25738        .update(cx, |workspace, window, cx| {
25739            workspace.active_pane().update(cx, |pane, cx| {
25740                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25741            })
25742        })
25743        .unwrap();
25744    close.await.unwrap();
25745    assert_eq!(
25746        3,
25747        requests_made.load(atomic::Ordering::Acquire),
25748        "After saving and closing all editors, no extra requests should be made"
25749    );
25750    workspace
25751        .update(cx, |workspace, _, cx| {
25752            assert!(
25753                workspace.active_item(cx).is_none(),
25754                "Should close all editors"
25755            )
25756        })
25757        .unwrap();
25758
25759    workspace
25760        .update(cx, |workspace, window, cx| {
25761            workspace.active_pane().update(cx, |pane, cx| {
25762                pane.navigate_backward(&Default::default(), window, cx);
25763            })
25764        })
25765        .unwrap();
25766    cx.executor().advance_clock(Duration::from_millis(100));
25767    cx.run_until_parked();
25768    let editor = workspace
25769        .update(cx, |workspace, _, cx| {
25770            workspace
25771                .active_item(cx)
25772                .expect("Should have reopened the editor again after navigating back")
25773                .downcast::<Editor>()
25774                .expect("Should be an editor")
25775        })
25776        .unwrap();
25777    color_request_handle.next().await.unwrap();
25778    assert_eq!(
25779        3,
25780        requests_made.load(atomic::Ordering::Acquire),
25781        "Cache should be reused on buffer close and reopen"
25782    );
25783    editor.update(cx, |editor, cx| {
25784        assert_eq!(
25785            vec![expected_color],
25786            extract_color_inlays(editor, cx),
25787            "Should have an initial inlay"
25788        );
25789    });
25790}
25791
25792#[gpui::test]
25793async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
25794    init_test(cx, |_| {});
25795    let (editor, cx) = cx.add_window_view(Editor::single_line);
25796    editor.update_in(cx, |editor, window, cx| {
25797        editor.set_text("oops\n\nwow\n", window, cx)
25798    });
25799    cx.run_until_parked();
25800    editor.update(cx, |editor, cx| {
25801        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
25802    });
25803    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
25804    cx.run_until_parked();
25805    editor.update(cx, |editor, cx| {
25806        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
25807    });
25808}
25809
25810#[gpui::test]
25811async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
25812    init_test(cx, |_| {});
25813
25814    cx.update(|cx| {
25815        register_project_item::<Editor>(cx);
25816    });
25817
25818    let fs = FakeFs::new(cx.executor());
25819    fs.insert_tree("/root1", json!({})).await;
25820    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
25821        .await;
25822
25823    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
25824    let (workspace, cx) =
25825        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25826
25827    let worktree_id = project.update(cx, |project, cx| {
25828        project.worktrees(cx).next().unwrap().read(cx).id()
25829    });
25830
25831    let handle = workspace
25832        .update_in(cx, |workspace, window, cx| {
25833            let project_path = (worktree_id, "one.pdf");
25834            workspace.open_path(project_path, None, true, window, cx)
25835        })
25836        .await
25837        .unwrap();
25838
25839    assert_eq!(
25840        handle.to_any().entity_type(),
25841        TypeId::of::<InvalidBufferView>()
25842    );
25843}
25844
25845#[gpui::test]
25846async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
25847    init_test(cx, |_| {});
25848
25849    let language = Arc::new(Language::new(
25850        LanguageConfig::default(),
25851        Some(tree_sitter_rust::LANGUAGE.into()),
25852    ));
25853
25854    // Test hierarchical sibling navigation
25855    let text = r#"
25856        fn outer() {
25857            if condition {
25858                let a = 1;
25859            }
25860            let b = 2;
25861        }
25862
25863        fn another() {
25864            let c = 3;
25865        }
25866    "#;
25867
25868    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
25869    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25870    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
25871
25872    // Wait for parsing to complete
25873    editor
25874        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
25875        .await;
25876
25877    editor.update_in(cx, |editor, window, cx| {
25878        // Start by selecting "let a = 1;" inside the if block
25879        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25880            s.select_display_ranges([
25881                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
25882            ]);
25883        });
25884
25885        let initial_selection = editor.selections.display_ranges(cx);
25886        assert_eq!(initial_selection.len(), 1, "Should have one selection");
25887
25888        // Test select next sibling - should move up levels to find the next sibling
25889        // Since "let a = 1;" has no siblings in the if block, it should move up
25890        // to find "let b = 2;" which is a sibling of the if block
25891        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25892        let next_selection = editor.selections.display_ranges(cx);
25893
25894        // Should have a selection and it should be different from the initial
25895        assert_eq!(
25896            next_selection.len(),
25897            1,
25898            "Should have one selection after next"
25899        );
25900        assert_ne!(
25901            next_selection[0], initial_selection[0],
25902            "Next sibling selection should be different"
25903        );
25904
25905        // Test hierarchical navigation by going to the end of the current function
25906        // and trying to navigate to the next function
25907        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25908            s.select_display_ranges([
25909                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
25910            ]);
25911        });
25912
25913        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25914        let function_next_selection = editor.selections.display_ranges(cx);
25915
25916        // Should move to the next function
25917        assert_eq!(
25918            function_next_selection.len(),
25919            1,
25920            "Should have one selection after function next"
25921        );
25922
25923        // Test select previous sibling navigation
25924        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
25925        let prev_selection = editor.selections.display_ranges(cx);
25926
25927        // Should have a selection and it should be different
25928        assert_eq!(
25929            prev_selection.len(),
25930            1,
25931            "Should have one selection after prev"
25932        );
25933        assert_ne!(
25934            prev_selection[0], function_next_selection[0],
25935            "Previous sibling selection should be different from next"
25936        );
25937    });
25938}
25939
25940#[gpui::test]
25941async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
25942    init_test(cx, |_| {});
25943
25944    let mut cx = EditorTestContext::new(cx).await;
25945    cx.set_state(
25946        "let ˇvariable = 42;
25947let another = variable + 1;
25948let result = variable * 2;",
25949    );
25950
25951    // Set up document highlights manually (simulating LSP response)
25952    cx.update_editor(|editor, _window, cx| {
25953        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
25954
25955        // Create highlights for "variable" occurrences
25956        let highlight_ranges = [
25957            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
25958            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
25959            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
25960        ];
25961
25962        let anchor_ranges: Vec<_> = highlight_ranges
25963            .iter()
25964            .map(|range| range.clone().to_anchors(&buffer_snapshot))
25965            .collect();
25966
25967        editor.highlight_background::<DocumentHighlightRead>(
25968            &anchor_ranges,
25969            |theme| theme.colors().editor_document_highlight_read_background,
25970            cx,
25971        );
25972    });
25973
25974    // Go to next highlight - should move to second "variable"
25975    cx.update_editor(|editor, window, cx| {
25976        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25977    });
25978    cx.assert_editor_state(
25979        "let variable = 42;
25980let another = ˇvariable + 1;
25981let result = variable * 2;",
25982    );
25983
25984    // Go to next highlight - should move to third "variable"
25985    cx.update_editor(|editor, window, cx| {
25986        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25987    });
25988    cx.assert_editor_state(
25989        "let variable = 42;
25990let another = variable + 1;
25991let result = ˇvariable * 2;",
25992    );
25993
25994    // Go to next highlight - should stay at third "variable" (no wrap-around)
25995    cx.update_editor(|editor, window, cx| {
25996        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25997    });
25998    cx.assert_editor_state(
25999        "let variable = 42;
26000let another = variable + 1;
26001let result = ˇvariable * 2;",
26002    );
26003
26004    // Now test going backwards from third position
26005    cx.update_editor(|editor, window, cx| {
26006        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26007    });
26008    cx.assert_editor_state(
26009        "let variable = 42;
26010let another = ˇvariable + 1;
26011let result = variable * 2;",
26012    );
26013
26014    // Go to previous highlight - should move to first "variable"
26015    cx.update_editor(|editor, window, cx| {
26016        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26017    });
26018    cx.assert_editor_state(
26019        "let ˇvariable = 42;
26020let another = variable + 1;
26021let result = variable * 2;",
26022    );
26023
26024    // Go to previous highlight - should stay on first "variable"
26025    cx.update_editor(|editor, window, cx| {
26026        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26027    });
26028    cx.assert_editor_state(
26029        "let ˇvariable = 42;
26030let another = variable + 1;
26031let result = variable * 2;",
26032    );
26033}
26034
26035#[gpui::test]
26036async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26037    cx: &mut gpui::TestAppContext,
26038) {
26039    init_test(cx, |_| {});
26040
26041    let url = "https://zed.dev";
26042
26043    let markdown_language = Arc::new(Language::new(
26044        LanguageConfig {
26045            name: "Markdown".into(),
26046            ..LanguageConfig::default()
26047        },
26048        None,
26049    ));
26050
26051    let mut cx = EditorTestContext::new(cx).await;
26052    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26053    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26054
26055    cx.update_editor(|editor, window, cx| {
26056        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26057        editor.paste(&Paste, window, cx);
26058    });
26059
26060    cx.assert_editor_state(&format!(
26061        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26062    ));
26063}
26064
26065#[gpui::test]
26066async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26067    cx: &mut gpui::TestAppContext,
26068) {
26069    init_test(cx, |_| {});
26070
26071    let url = "https://zed.dev";
26072
26073    let markdown_language = Arc::new(Language::new(
26074        LanguageConfig {
26075            name: "Markdown".into(),
26076            ..LanguageConfig::default()
26077        },
26078        None,
26079    ));
26080
26081    let mut cx = EditorTestContext::new(cx).await;
26082    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26083    cx.set_state(&format!(
26084        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26085    ));
26086
26087    cx.update_editor(|editor, window, cx| {
26088        editor.copy(&Copy, window, cx);
26089    });
26090
26091    cx.set_state(&format!(
26092        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26093    ));
26094
26095    cx.update_editor(|editor, window, cx| {
26096        editor.paste(&Paste, window, cx);
26097    });
26098
26099    cx.assert_editor_state(&format!(
26100        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26101    ));
26102}
26103
26104#[gpui::test]
26105async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26106    cx: &mut gpui::TestAppContext,
26107) {
26108    init_test(cx, |_| {});
26109
26110    let url = "https://zed.dev";
26111
26112    let markdown_language = Arc::new(Language::new(
26113        LanguageConfig {
26114            name: "Markdown".into(),
26115            ..LanguageConfig::default()
26116        },
26117        None,
26118    ));
26119
26120    let mut cx = EditorTestContext::new(cx).await;
26121    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26122    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26123
26124    cx.update_editor(|editor, window, cx| {
26125        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26126        editor.paste(&Paste, window, cx);
26127    });
26128
26129    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26130}
26131
26132#[gpui::test]
26133async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26134    cx: &mut gpui::TestAppContext,
26135) {
26136    init_test(cx, |_| {});
26137
26138    let text = "Awesome";
26139
26140    let markdown_language = Arc::new(Language::new(
26141        LanguageConfig {
26142            name: "Markdown".into(),
26143            ..LanguageConfig::default()
26144        },
26145        None,
26146    ));
26147
26148    let mut cx = EditorTestContext::new(cx).await;
26149    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26150    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26151
26152    cx.update_editor(|editor, window, cx| {
26153        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26154        editor.paste(&Paste, window, cx);
26155    });
26156
26157    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26158}
26159
26160#[gpui::test]
26161async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26162    cx: &mut gpui::TestAppContext,
26163) {
26164    init_test(cx, |_| {});
26165
26166    let url = "https://zed.dev";
26167
26168    let markdown_language = Arc::new(Language::new(
26169        LanguageConfig {
26170            name: "Rust".into(),
26171            ..LanguageConfig::default()
26172        },
26173        None,
26174    ));
26175
26176    let mut cx = EditorTestContext::new(cx).await;
26177    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26178    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26179
26180    cx.update_editor(|editor, window, cx| {
26181        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26182        editor.paste(&Paste, window, cx);
26183    });
26184
26185    cx.assert_editor_state(&format!(
26186        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26187    ));
26188}
26189
26190#[gpui::test]
26191async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26192    cx: &mut TestAppContext,
26193) {
26194    init_test(cx, |_| {});
26195
26196    let url = "https://zed.dev";
26197
26198    let markdown_language = Arc::new(Language::new(
26199        LanguageConfig {
26200            name: "Markdown".into(),
26201            ..LanguageConfig::default()
26202        },
26203        None,
26204    ));
26205
26206    let (editor, cx) = cx.add_window_view(|window, cx| {
26207        let multi_buffer = MultiBuffer::build_multi(
26208            [
26209                ("this will embed -> link", vec![Point::row_range(0..1)]),
26210                ("this will replace -> link", vec![Point::row_range(0..1)]),
26211            ],
26212            cx,
26213        );
26214        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26215        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26216            s.select_ranges(vec![
26217                Point::new(0, 19)..Point::new(0, 23),
26218                Point::new(1, 21)..Point::new(1, 25),
26219            ])
26220        });
26221        let first_buffer_id = multi_buffer
26222            .read(cx)
26223            .excerpt_buffer_ids()
26224            .into_iter()
26225            .next()
26226            .unwrap();
26227        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26228        first_buffer.update(cx, |buffer, cx| {
26229            buffer.set_language(Some(markdown_language.clone()), cx);
26230        });
26231
26232        editor
26233    });
26234    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26235
26236    cx.update_editor(|editor, window, cx| {
26237        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26238        editor.paste(&Paste, window, cx);
26239    });
26240
26241    cx.assert_editor_state(&format!(
26242        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26243    ));
26244}
26245
26246#[track_caller]
26247fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26248    editor
26249        .all_inlays(cx)
26250        .into_iter()
26251        .filter_map(|inlay| inlay.get_color())
26252        .map(Rgba::from)
26253        .collect()
26254}