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            let requested_code_actions = params.context.only.expect("Expected code action request");
11929            assert_eq!(requested_code_actions.len(), 1);
11930
11931            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
11932            let code_action = match requested_code_actions[0].as_str() {
11933                "code-action-1" => lsp::CodeAction {
11934                    kind: Some("code-action-1".into()),
11935                    edit: Some(lsp::WorkspaceEdit::new(
11936                        [(
11937                            uri,
11938                            vec![lsp::TextEdit::new(
11939                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11940                                "applied-code-action-1-edit\n".to_string(),
11941                            )],
11942                        )]
11943                        .into_iter()
11944                        .collect(),
11945                    )),
11946                    command: Some(lsp::Command {
11947                        command: "the-command-for-code-action-1".into(),
11948                        ..Default::default()
11949                    }),
11950                    ..Default::default()
11951                },
11952                "code-action-2" => lsp::CodeAction {
11953                    kind: Some("code-action-2".into()),
11954                    edit: Some(lsp::WorkspaceEdit::new(
11955                        [(
11956                            uri,
11957                            vec![lsp::TextEdit::new(
11958                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11959                                "applied-code-action-2-edit\n".to_string(),
11960                            )],
11961                        )]
11962                        .into_iter()
11963                        .collect(),
11964                    )),
11965                    ..Default::default()
11966                },
11967                req => panic!("Unexpected code action request: {:?}", req),
11968            };
11969            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
11970                code_action,
11971            )]))
11972        },
11973    );
11974
11975    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
11976        move |params, _| async move { Ok(params) }
11977    });
11978
11979    let command_lock = Arc::new(futures::lock::Mutex::new(()));
11980    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
11981        let fake = fake_server.clone();
11982        let lock = command_lock.clone();
11983        move |params, _| {
11984            assert_eq!(params.command, "the-command-for-code-action-1");
11985            let fake = fake.clone();
11986            let lock = lock.clone();
11987            async move {
11988                lock.lock().await;
11989                fake.server
11990                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
11991                        label: None,
11992                        edit: lsp::WorkspaceEdit {
11993                            changes: Some(
11994                                [(
11995                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
11996                                    vec![lsp::TextEdit {
11997                                        range: lsp::Range::new(
11998                                            lsp::Position::new(0, 0),
11999                                            lsp::Position::new(0, 0),
12000                                        ),
12001                                        new_text: "applied-code-action-1-command\n".into(),
12002                                    }],
12003                                )]
12004                                .into_iter()
12005                                .collect(),
12006                            ),
12007                            ..Default::default()
12008                        },
12009                    })
12010                    .await
12011                    .into_response()
12012                    .unwrap();
12013                Ok(Some(json!(null)))
12014            }
12015        }
12016    });
12017
12018    cx.executor().start_waiting();
12019    editor
12020        .update_in(cx, |editor, window, cx| {
12021            editor.perform_format(
12022                project.clone(),
12023                FormatTrigger::Manual,
12024                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12025                window,
12026                cx,
12027            )
12028        })
12029        .unwrap()
12030        .await;
12031    editor.update(cx, |editor, cx| {
12032        assert_eq!(
12033            editor.text(cx),
12034            r#"
12035                applied-code-action-2-edit
12036                applied-code-action-1-command
12037                applied-code-action-1-edit
12038                applied-formatting
12039                one
12040                two
12041                three
12042            "#
12043            .unindent()
12044        );
12045    });
12046
12047    editor.update_in(cx, |editor, window, cx| {
12048        editor.undo(&Default::default(), window, cx);
12049        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12050    });
12051
12052    // Perform a manual edit while waiting for an LSP command
12053    // that's being run as part of a formatting code action.
12054    let lock_guard = command_lock.lock().await;
12055    let format = editor
12056        .update_in(cx, |editor, window, cx| {
12057            editor.perform_format(
12058                project.clone(),
12059                FormatTrigger::Manual,
12060                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12061                window,
12062                cx,
12063            )
12064        })
12065        .unwrap();
12066    cx.run_until_parked();
12067    editor.update(cx, |editor, cx| {
12068        assert_eq!(
12069            editor.text(cx),
12070            r#"
12071                applied-code-action-1-edit
12072                applied-formatting
12073                one
12074                two
12075                three
12076            "#
12077            .unindent()
12078        );
12079
12080        editor.buffer.update(cx, |buffer, cx| {
12081            let ix = buffer.len(cx);
12082            buffer.edit([(ix..ix, "edited\n")], None, cx);
12083        });
12084    });
12085
12086    // Allow the LSP command to proceed. Because the buffer was edited,
12087    // the second code action will not be run.
12088    drop(lock_guard);
12089    format.await;
12090    editor.update_in(cx, |editor, window, cx| {
12091        assert_eq!(
12092            editor.text(cx),
12093            r#"
12094                applied-code-action-1-command
12095                applied-code-action-1-edit
12096                applied-formatting
12097                one
12098                two
12099                three
12100                edited
12101            "#
12102            .unindent()
12103        );
12104
12105        // The manual edit is undone first, because it is the last thing the user did
12106        // (even though the command completed afterwards).
12107        editor.undo(&Default::default(), window, cx);
12108        assert_eq!(
12109            editor.text(cx),
12110            r#"
12111                applied-code-action-1-command
12112                applied-code-action-1-edit
12113                applied-formatting
12114                one
12115                two
12116                three
12117            "#
12118            .unindent()
12119        );
12120
12121        // All the formatting (including the command, which completed after the manual edit)
12122        // is undone together.
12123        editor.undo(&Default::default(), window, cx);
12124        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12125    });
12126}
12127
12128#[gpui::test]
12129async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12130    init_test(cx, |settings| {
12131        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
12132            Formatter::LanguageServer { name: None },
12133        ])))
12134    });
12135
12136    let fs = FakeFs::new(cx.executor());
12137    fs.insert_file(path!("/file.ts"), Default::default()).await;
12138
12139    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12140
12141    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12142    language_registry.add(Arc::new(Language::new(
12143        LanguageConfig {
12144            name: "TypeScript".into(),
12145            matcher: LanguageMatcher {
12146                path_suffixes: vec!["ts".to_string()],
12147                ..Default::default()
12148            },
12149            ..LanguageConfig::default()
12150        },
12151        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12152    )));
12153    update_test_language_settings(cx, |settings| {
12154        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12155    });
12156    let mut fake_servers = language_registry.register_fake_lsp(
12157        "TypeScript",
12158        FakeLspAdapter {
12159            capabilities: lsp::ServerCapabilities {
12160                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12161                ..Default::default()
12162            },
12163            ..Default::default()
12164        },
12165    );
12166
12167    let buffer = project
12168        .update(cx, |project, cx| {
12169            project.open_local_buffer(path!("/file.ts"), cx)
12170        })
12171        .await
12172        .unwrap();
12173
12174    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12175    let (editor, cx) = cx.add_window_view(|window, cx| {
12176        build_editor_with_project(project.clone(), buffer, window, cx)
12177    });
12178    editor.update_in(cx, |editor, window, cx| {
12179        editor.set_text(
12180            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12181            window,
12182            cx,
12183        )
12184    });
12185
12186    cx.executor().start_waiting();
12187    let fake_server = fake_servers.next().await.unwrap();
12188
12189    let format = editor
12190        .update_in(cx, |editor, window, cx| {
12191            editor.perform_code_action_kind(
12192                project.clone(),
12193                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12194                window,
12195                cx,
12196            )
12197        })
12198        .unwrap();
12199    fake_server
12200        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12201            assert_eq!(
12202                params.text_document.uri,
12203                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12204            );
12205            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12206                lsp::CodeAction {
12207                    title: "Organize Imports".to_string(),
12208                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12209                    edit: Some(lsp::WorkspaceEdit {
12210                        changes: Some(
12211                            [(
12212                                params.text_document.uri.clone(),
12213                                vec![lsp::TextEdit::new(
12214                                    lsp::Range::new(
12215                                        lsp::Position::new(1, 0),
12216                                        lsp::Position::new(2, 0),
12217                                    ),
12218                                    "".to_string(),
12219                                )],
12220                            )]
12221                            .into_iter()
12222                            .collect(),
12223                        ),
12224                        ..Default::default()
12225                    }),
12226                    ..Default::default()
12227                },
12228            )]))
12229        })
12230        .next()
12231        .await;
12232    cx.executor().start_waiting();
12233    format.await;
12234    assert_eq!(
12235        editor.update(cx, |editor, cx| editor.text(cx)),
12236        "import { a } from 'module';\n\nconst x = a;\n"
12237    );
12238
12239    editor.update_in(cx, |editor, window, cx| {
12240        editor.set_text(
12241            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12242            window,
12243            cx,
12244        )
12245    });
12246    // Ensure we don't lock if code action hangs.
12247    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12248        move |params, _| async move {
12249            assert_eq!(
12250                params.text_document.uri,
12251                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12252            );
12253            futures::future::pending::<()>().await;
12254            unreachable!()
12255        },
12256    );
12257    let format = editor
12258        .update_in(cx, |editor, window, cx| {
12259            editor.perform_code_action_kind(
12260                project,
12261                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12262                window,
12263                cx,
12264            )
12265        })
12266        .unwrap();
12267    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12268    cx.executor().start_waiting();
12269    format.await;
12270    assert_eq!(
12271        editor.update(cx, |editor, cx| editor.text(cx)),
12272        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12273    );
12274}
12275
12276#[gpui::test]
12277async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12278    init_test(cx, |_| {});
12279
12280    let mut cx = EditorLspTestContext::new_rust(
12281        lsp::ServerCapabilities {
12282            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12283            ..Default::default()
12284        },
12285        cx,
12286    )
12287    .await;
12288
12289    cx.set_state(indoc! {"
12290        one.twoˇ
12291    "});
12292
12293    // The format request takes a long time. When it completes, it inserts
12294    // a newline and an indent before the `.`
12295    cx.lsp
12296        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12297            let executor = cx.background_executor().clone();
12298            async move {
12299                executor.timer(Duration::from_millis(100)).await;
12300                Ok(Some(vec![lsp::TextEdit {
12301                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12302                    new_text: "\n    ".into(),
12303                }]))
12304            }
12305        });
12306
12307    // Submit a format request.
12308    let format_1 = cx
12309        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12310        .unwrap();
12311    cx.executor().run_until_parked();
12312
12313    // Submit a second format request.
12314    let format_2 = cx
12315        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12316        .unwrap();
12317    cx.executor().run_until_parked();
12318
12319    // Wait for both format requests to complete
12320    cx.executor().advance_clock(Duration::from_millis(200));
12321    cx.executor().start_waiting();
12322    format_1.await.unwrap();
12323    cx.executor().start_waiting();
12324    format_2.await.unwrap();
12325
12326    // The formatting edits only happens once.
12327    cx.assert_editor_state(indoc! {"
12328        one
12329            .twoˇ
12330    "});
12331}
12332
12333#[gpui::test]
12334async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12335    init_test(cx, |settings| {
12336        settings.defaults.formatter = Some(SelectedFormatter::Auto)
12337    });
12338
12339    let mut cx = EditorLspTestContext::new_rust(
12340        lsp::ServerCapabilities {
12341            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12342            ..Default::default()
12343        },
12344        cx,
12345    )
12346    .await;
12347
12348    // Set up a buffer white some trailing whitespace and no trailing newline.
12349    cx.set_state(
12350        &[
12351            "one ",   //
12352            "twoˇ",   //
12353            "three ", //
12354            "four",   //
12355        ]
12356        .join("\n"),
12357    );
12358
12359    // Submit a format request.
12360    let format = cx
12361        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12362        .unwrap();
12363
12364    // Record which buffer changes have been sent to the language server
12365    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12366    cx.lsp
12367        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12368            let buffer_changes = buffer_changes.clone();
12369            move |params, _| {
12370                buffer_changes.lock().extend(
12371                    params
12372                        .content_changes
12373                        .into_iter()
12374                        .map(|e| (e.range.unwrap(), e.text)),
12375                );
12376            }
12377        });
12378
12379    // Handle formatting requests to the language server.
12380    cx.lsp
12381        .set_request_handler::<lsp::request::Formatting, _, _>({
12382            let buffer_changes = buffer_changes.clone();
12383            move |_, _| {
12384                // When formatting is requested, trailing whitespace has already been stripped,
12385                // and the trailing newline has already been added.
12386                assert_eq!(
12387                    &buffer_changes.lock()[1..],
12388                    &[
12389                        (
12390                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12391                            "".into()
12392                        ),
12393                        (
12394                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12395                            "".into()
12396                        ),
12397                        (
12398                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12399                            "\n".into()
12400                        ),
12401                    ]
12402                );
12403
12404                // Insert blank lines between each line of the buffer.
12405                async move {
12406                    Ok(Some(vec![
12407                        lsp::TextEdit {
12408                            range: lsp::Range::new(
12409                                lsp::Position::new(1, 0),
12410                                lsp::Position::new(1, 0),
12411                            ),
12412                            new_text: "\n".into(),
12413                        },
12414                        lsp::TextEdit {
12415                            range: lsp::Range::new(
12416                                lsp::Position::new(2, 0),
12417                                lsp::Position::new(2, 0),
12418                            ),
12419                            new_text: "\n".into(),
12420                        },
12421                    ]))
12422                }
12423            }
12424        });
12425
12426    // After formatting the buffer, the trailing whitespace is stripped,
12427    // a newline is appended, and the edits provided by the language server
12428    // have been applied.
12429    format.await.unwrap();
12430    cx.assert_editor_state(
12431        &[
12432            "one",   //
12433            "",      //
12434            "twoˇ",  //
12435            "",      //
12436            "three", //
12437            "four",  //
12438            "",      //
12439        ]
12440        .join("\n"),
12441    );
12442
12443    // Undoing the formatting undoes the trailing whitespace removal, the
12444    // trailing newline, and the LSP edits.
12445    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12446    cx.assert_editor_state(
12447        &[
12448            "one ",   //
12449            "twoˇ",   //
12450            "three ", //
12451            "four",   //
12452        ]
12453        .join("\n"),
12454    );
12455}
12456
12457#[gpui::test]
12458async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12459    cx: &mut TestAppContext,
12460) {
12461    init_test(cx, |_| {});
12462
12463    cx.update(|cx| {
12464        cx.update_global::<SettingsStore, _>(|settings, cx| {
12465            settings.update_user_settings(cx, |settings| {
12466                settings.editor.auto_signature_help = Some(true);
12467            });
12468        });
12469    });
12470
12471    let mut cx = EditorLspTestContext::new_rust(
12472        lsp::ServerCapabilities {
12473            signature_help_provider: Some(lsp::SignatureHelpOptions {
12474                ..Default::default()
12475            }),
12476            ..Default::default()
12477        },
12478        cx,
12479    )
12480    .await;
12481
12482    let language = Language::new(
12483        LanguageConfig {
12484            name: "Rust".into(),
12485            brackets: BracketPairConfig {
12486                pairs: vec![
12487                    BracketPair {
12488                        start: "{".to_string(),
12489                        end: "}".to_string(),
12490                        close: true,
12491                        surround: true,
12492                        newline: true,
12493                    },
12494                    BracketPair {
12495                        start: "(".to_string(),
12496                        end: ")".to_string(),
12497                        close: true,
12498                        surround: true,
12499                        newline: true,
12500                    },
12501                    BracketPair {
12502                        start: "/*".to_string(),
12503                        end: " */".to_string(),
12504                        close: true,
12505                        surround: true,
12506                        newline: true,
12507                    },
12508                    BracketPair {
12509                        start: "[".to_string(),
12510                        end: "]".to_string(),
12511                        close: false,
12512                        surround: false,
12513                        newline: true,
12514                    },
12515                    BracketPair {
12516                        start: "\"".to_string(),
12517                        end: "\"".to_string(),
12518                        close: true,
12519                        surround: true,
12520                        newline: false,
12521                    },
12522                    BracketPair {
12523                        start: "<".to_string(),
12524                        end: ">".to_string(),
12525                        close: false,
12526                        surround: true,
12527                        newline: true,
12528                    },
12529                ],
12530                ..Default::default()
12531            },
12532            autoclose_before: "})]".to_string(),
12533            ..Default::default()
12534        },
12535        Some(tree_sitter_rust::LANGUAGE.into()),
12536    );
12537    let language = Arc::new(language);
12538
12539    cx.language_registry().add(language.clone());
12540    cx.update_buffer(|buffer, cx| {
12541        buffer.set_language(Some(language), cx);
12542    });
12543
12544    cx.set_state(
12545        &r#"
12546            fn main() {
12547                sampleˇ
12548            }
12549        "#
12550        .unindent(),
12551    );
12552
12553    cx.update_editor(|editor, window, cx| {
12554        editor.handle_input("(", window, cx);
12555    });
12556    cx.assert_editor_state(
12557        &"
12558            fn main() {
12559                sample(ˇ)
12560            }
12561        "
12562        .unindent(),
12563    );
12564
12565    let mocked_response = lsp::SignatureHelp {
12566        signatures: vec![lsp::SignatureInformation {
12567            label: "fn sample(param1: u8, param2: u8)".to_string(),
12568            documentation: None,
12569            parameters: Some(vec![
12570                lsp::ParameterInformation {
12571                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12572                    documentation: None,
12573                },
12574                lsp::ParameterInformation {
12575                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12576                    documentation: None,
12577                },
12578            ]),
12579            active_parameter: None,
12580        }],
12581        active_signature: Some(0),
12582        active_parameter: Some(0),
12583    };
12584    handle_signature_help_request(&mut cx, mocked_response).await;
12585
12586    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12587        .await;
12588
12589    cx.editor(|editor, _, _| {
12590        let signature_help_state = editor.signature_help_state.popover().cloned();
12591        let signature = signature_help_state.unwrap();
12592        assert_eq!(
12593            signature.signatures[signature.current_signature].label,
12594            "fn sample(param1: u8, param2: u8)"
12595        );
12596    });
12597}
12598
12599#[gpui::test]
12600async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12601    init_test(cx, |_| {});
12602
12603    cx.update(|cx| {
12604        cx.update_global::<SettingsStore, _>(|settings, cx| {
12605            settings.update_user_settings(cx, |settings| {
12606                settings.editor.auto_signature_help = Some(false);
12607                settings.editor.show_signature_help_after_edits = Some(false);
12608            });
12609        });
12610    });
12611
12612    let mut cx = EditorLspTestContext::new_rust(
12613        lsp::ServerCapabilities {
12614            signature_help_provider: Some(lsp::SignatureHelpOptions {
12615                ..Default::default()
12616            }),
12617            ..Default::default()
12618        },
12619        cx,
12620    )
12621    .await;
12622
12623    let language = Language::new(
12624        LanguageConfig {
12625            name: "Rust".into(),
12626            brackets: BracketPairConfig {
12627                pairs: vec![
12628                    BracketPair {
12629                        start: "{".to_string(),
12630                        end: "}".to_string(),
12631                        close: true,
12632                        surround: true,
12633                        newline: true,
12634                    },
12635                    BracketPair {
12636                        start: "(".to_string(),
12637                        end: ")".to_string(),
12638                        close: true,
12639                        surround: true,
12640                        newline: true,
12641                    },
12642                    BracketPair {
12643                        start: "/*".to_string(),
12644                        end: " */".to_string(),
12645                        close: true,
12646                        surround: true,
12647                        newline: true,
12648                    },
12649                    BracketPair {
12650                        start: "[".to_string(),
12651                        end: "]".to_string(),
12652                        close: false,
12653                        surround: false,
12654                        newline: true,
12655                    },
12656                    BracketPair {
12657                        start: "\"".to_string(),
12658                        end: "\"".to_string(),
12659                        close: true,
12660                        surround: true,
12661                        newline: false,
12662                    },
12663                    BracketPair {
12664                        start: "<".to_string(),
12665                        end: ">".to_string(),
12666                        close: false,
12667                        surround: true,
12668                        newline: true,
12669                    },
12670                ],
12671                ..Default::default()
12672            },
12673            autoclose_before: "})]".to_string(),
12674            ..Default::default()
12675        },
12676        Some(tree_sitter_rust::LANGUAGE.into()),
12677    );
12678    let language = Arc::new(language);
12679
12680    cx.language_registry().add(language.clone());
12681    cx.update_buffer(|buffer, cx| {
12682        buffer.set_language(Some(language), cx);
12683    });
12684
12685    // Ensure that signature_help is not called when no signature help is enabled.
12686    cx.set_state(
12687        &r#"
12688            fn main() {
12689                sampleˇ
12690            }
12691        "#
12692        .unindent(),
12693    );
12694    cx.update_editor(|editor, window, cx| {
12695        editor.handle_input("(", window, cx);
12696    });
12697    cx.assert_editor_state(
12698        &"
12699            fn main() {
12700                sample(ˇ)
12701            }
12702        "
12703        .unindent(),
12704    );
12705    cx.editor(|editor, _, _| {
12706        assert!(editor.signature_help_state.task().is_none());
12707    });
12708
12709    let mocked_response = lsp::SignatureHelp {
12710        signatures: vec![lsp::SignatureInformation {
12711            label: "fn sample(param1: u8, param2: u8)".to_string(),
12712            documentation: None,
12713            parameters: Some(vec![
12714                lsp::ParameterInformation {
12715                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12716                    documentation: None,
12717                },
12718                lsp::ParameterInformation {
12719                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12720                    documentation: None,
12721                },
12722            ]),
12723            active_parameter: None,
12724        }],
12725        active_signature: Some(0),
12726        active_parameter: Some(0),
12727    };
12728
12729    // Ensure that signature_help is called when enabled afte edits
12730    cx.update(|_, cx| {
12731        cx.update_global::<SettingsStore, _>(|settings, cx| {
12732            settings.update_user_settings(cx, |settings| {
12733                settings.editor.auto_signature_help = Some(false);
12734                settings.editor.show_signature_help_after_edits = Some(true);
12735            });
12736        });
12737    });
12738    cx.set_state(
12739        &r#"
12740            fn main() {
12741                sampleˇ
12742            }
12743        "#
12744        .unindent(),
12745    );
12746    cx.update_editor(|editor, window, cx| {
12747        editor.handle_input("(", window, cx);
12748    });
12749    cx.assert_editor_state(
12750        &"
12751            fn main() {
12752                sample(ˇ)
12753            }
12754        "
12755        .unindent(),
12756    );
12757    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12758    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12759        .await;
12760    cx.update_editor(|editor, _, _| {
12761        let signature_help_state = editor.signature_help_state.popover().cloned();
12762        assert!(signature_help_state.is_some());
12763        let signature = signature_help_state.unwrap();
12764        assert_eq!(
12765            signature.signatures[signature.current_signature].label,
12766            "fn sample(param1: u8, param2: u8)"
12767        );
12768        editor.signature_help_state = SignatureHelpState::default();
12769    });
12770
12771    // Ensure that signature_help is called when auto signature help override is enabled
12772    cx.update(|_, cx| {
12773        cx.update_global::<SettingsStore, _>(|settings, cx| {
12774            settings.update_user_settings(cx, |settings| {
12775                settings.editor.auto_signature_help = Some(true);
12776                settings.editor.show_signature_help_after_edits = Some(false);
12777            });
12778        });
12779    });
12780    cx.set_state(
12781        &r#"
12782            fn main() {
12783                sampleˇ
12784            }
12785        "#
12786        .unindent(),
12787    );
12788    cx.update_editor(|editor, window, cx| {
12789        editor.handle_input("(", window, cx);
12790    });
12791    cx.assert_editor_state(
12792        &"
12793            fn main() {
12794                sample(ˇ)
12795            }
12796        "
12797        .unindent(),
12798    );
12799    handle_signature_help_request(&mut cx, mocked_response).await;
12800    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12801        .await;
12802    cx.editor(|editor, _, _| {
12803        let signature_help_state = editor.signature_help_state.popover().cloned();
12804        assert!(signature_help_state.is_some());
12805        let signature = signature_help_state.unwrap();
12806        assert_eq!(
12807            signature.signatures[signature.current_signature].label,
12808            "fn sample(param1: u8, param2: u8)"
12809        );
12810    });
12811}
12812
12813#[gpui::test]
12814async fn test_signature_help(cx: &mut TestAppContext) {
12815    init_test(cx, |_| {});
12816    cx.update(|cx| {
12817        cx.update_global::<SettingsStore, _>(|settings, cx| {
12818            settings.update_user_settings(cx, |settings| {
12819                settings.editor.auto_signature_help = Some(true);
12820            });
12821        });
12822    });
12823
12824    let mut cx = EditorLspTestContext::new_rust(
12825        lsp::ServerCapabilities {
12826            signature_help_provider: Some(lsp::SignatureHelpOptions {
12827                ..Default::default()
12828            }),
12829            ..Default::default()
12830        },
12831        cx,
12832    )
12833    .await;
12834
12835    // A test that directly calls `show_signature_help`
12836    cx.update_editor(|editor, window, cx| {
12837        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12838    });
12839
12840    let mocked_response = lsp::SignatureHelp {
12841        signatures: vec![lsp::SignatureInformation {
12842            label: "fn sample(param1: u8, param2: u8)".to_string(),
12843            documentation: None,
12844            parameters: Some(vec![
12845                lsp::ParameterInformation {
12846                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12847                    documentation: None,
12848                },
12849                lsp::ParameterInformation {
12850                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12851                    documentation: None,
12852                },
12853            ]),
12854            active_parameter: None,
12855        }],
12856        active_signature: Some(0),
12857        active_parameter: Some(0),
12858    };
12859    handle_signature_help_request(&mut cx, mocked_response).await;
12860
12861    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12862        .await;
12863
12864    cx.editor(|editor, _, _| {
12865        let signature_help_state = editor.signature_help_state.popover().cloned();
12866        assert!(signature_help_state.is_some());
12867        let signature = signature_help_state.unwrap();
12868        assert_eq!(
12869            signature.signatures[signature.current_signature].label,
12870            "fn sample(param1: u8, param2: u8)"
12871        );
12872    });
12873
12874    // When exiting outside from inside the brackets, `signature_help` is closed.
12875    cx.set_state(indoc! {"
12876        fn main() {
12877            sample(ˇ);
12878        }
12879
12880        fn sample(param1: u8, param2: u8) {}
12881    "});
12882
12883    cx.update_editor(|editor, window, cx| {
12884        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12885            s.select_ranges([0..0])
12886        });
12887    });
12888
12889    let mocked_response = lsp::SignatureHelp {
12890        signatures: Vec::new(),
12891        active_signature: None,
12892        active_parameter: None,
12893    };
12894    handle_signature_help_request(&mut cx, mocked_response).await;
12895
12896    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12897        .await;
12898
12899    cx.editor(|editor, _, _| {
12900        assert!(!editor.signature_help_state.is_shown());
12901    });
12902
12903    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
12904    cx.set_state(indoc! {"
12905        fn main() {
12906            sample(ˇ);
12907        }
12908
12909        fn sample(param1: u8, param2: u8) {}
12910    "});
12911
12912    let mocked_response = lsp::SignatureHelp {
12913        signatures: vec![lsp::SignatureInformation {
12914            label: "fn sample(param1: u8, param2: u8)".to_string(),
12915            documentation: None,
12916            parameters: Some(vec![
12917                lsp::ParameterInformation {
12918                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12919                    documentation: None,
12920                },
12921                lsp::ParameterInformation {
12922                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12923                    documentation: None,
12924                },
12925            ]),
12926            active_parameter: None,
12927        }],
12928        active_signature: Some(0),
12929        active_parameter: Some(0),
12930    };
12931    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12932    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12933        .await;
12934    cx.editor(|editor, _, _| {
12935        assert!(editor.signature_help_state.is_shown());
12936    });
12937
12938    // Restore the popover with more parameter input
12939    cx.set_state(indoc! {"
12940        fn main() {
12941            sample(param1, param2ˇ);
12942        }
12943
12944        fn sample(param1: u8, param2: u8) {}
12945    "});
12946
12947    let mocked_response = lsp::SignatureHelp {
12948        signatures: vec![lsp::SignatureInformation {
12949            label: "fn sample(param1: u8, param2: u8)".to_string(),
12950            documentation: None,
12951            parameters: Some(vec![
12952                lsp::ParameterInformation {
12953                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12954                    documentation: None,
12955                },
12956                lsp::ParameterInformation {
12957                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12958                    documentation: None,
12959                },
12960            ]),
12961            active_parameter: None,
12962        }],
12963        active_signature: Some(0),
12964        active_parameter: Some(1),
12965    };
12966    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12967    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12968        .await;
12969
12970    // When selecting a range, the popover is gone.
12971    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
12972    cx.update_editor(|editor, window, cx| {
12973        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12974            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12975        })
12976    });
12977    cx.assert_editor_state(indoc! {"
12978        fn main() {
12979            sample(param1, «ˇparam2»);
12980        }
12981
12982        fn sample(param1: u8, param2: u8) {}
12983    "});
12984    cx.editor(|editor, _, _| {
12985        assert!(!editor.signature_help_state.is_shown());
12986    });
12987
12988    // When unselecting again, the popover is back if within the brackets.
12989    cx.update_editor(|editor, window, cx| {
12990        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12991            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12992        })
12993    });
12994    cx.assert_editor_state(indoc! {"
12995        fn main() {
12996            sample(param1, ˇparam2);
12997        }
12998
12999        fn sample(param1: u8, param2: u8) {}
13000    "});
13001    handle_signature_help_request(&mut cx, mocked_response).await;
13002    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13003        .await;
13004    cx.editor(|editor, _, _| {
13005        assert!(editor.signature_help_state.is_shown());
13006    });
13007
13008    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13009    cx.update_editor(|editor, window, cx| {
13010        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13011            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13012            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13013        })
13014    });
13015    cx.assert_editor_state(indoc! {"
13016        fn main() {
13017            sample(param1, ˇparam2);
13018        }
13019
13020        fn sample(param1: u8, param2: u8) {}
13021    "});
13022
13023    let mocked_response = lsp::SignatureHelp {
13024        signatures: vec![lsp::SignatureInformation {
13025            label: "fn sample(param1: u8, param2: u8)".to_string(),
13026            documentation: None,
13027            parameters: Some(vec![
13028                lsp::ParameterInformation {
13029                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13030                    documentation: None,
13031                },
13032                lsp::ParameterInformation {
13033                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13034                    documentation: None,
13035                },
13036            ]),
13037            active_parameter: None,
13038        }],
13039        active_signature: Some(0),
13040        active_parameter: Some(1),
13041    };
13042    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13043    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13044        .await;
13045    cx.update_editor(|editor, _, cx| {
13046        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13047    });
13048    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13049        .await;
13050    cx.update_editor(|editor, window, cx| {
13051        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13052            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13053        })
13054    });
13055    cx.assert_editor_state(indoc! {"
13056        fn main() {
13057            sample(param1, «ˇparam2»);
13058        }
13059
13060        fn sample(param1: u8, param2: u8) {}
13061    "});
13062    cx.update_editor(|editor, window, cx| {
13063        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13064            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13065        })
13066    });
13067    cx.assert_editor_state(indoc! {"
13068        fn main() {
13069            sample(param1, ˇparam2);
13070        }
13071
13072        fn sample(param1: u8, param2: u8) {}
13073    "});
13074    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13075        .await;
13076}
13077
13078#[gpui::test]
13079async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13080    init_test(cx, |_| {});
13081
13082    let mut cx = EditorLspTestContext::new_rust(
13083        lsp::ServerCapabilities {
13084            signature_help_provider: Some(lsp::SignatureHelpOptions {
13085                ..Default::default()
13086            }),
13087            ..Default::default()
13088        },
13089        cx,
13090    )
13091    .await;
13092
13093    cx.set_state(indoc! {"
13094        fn main() {
13095            overloadedˇ
13096        }
13097    "});
13098
13099    cx.update_editor(|editor, window, cx| {
13100        editor.handle_input("(", window, cx);
13101        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13102    });
13103
13104    // Mock response with 3 signatures
13105    let mocked_response = lsp::SignatureHelp {
13106        signatures: vec![
13107            lsp::SignatureInformation {
13108                label: "fn overloaded(x: i32)".to_string(),
13109                documentation: None,
13110                parameters: Some(vec![lsp::ParameterInformation {
13111                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13112                    documentation: None,
13113                }]),
13114                active_parameter: None,
13115            },
13116            lsp::SignatureInformation {
13117                label: "fn overloaded(x: i32, y: i32)".to_string(),
13118                documentation: None,
13119                parameters: Some(vec![
13120                    lsp::ParameterInformation {
13121                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13122                        documentation: None,
13123                    },
13124                    lsp::ParameterInformation {
13125                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13126                        documentation: None,
13127                    },
13128                ]),
13129                active_parameter: None,
13130            },
13131            lsp::SignatureInformation {
13132                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13133                documentation: None,
13134                parameters: Some(vec![
13135                    lsp::ParameterInformation {
13136                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13137                        documentation: None,
13138                    },
13139                    lsp::ParameterInformation {
13140                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13141                        documentation: None,
13142                    },
13143                    lsp::ParameterInformation {
13144                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13145                        documentation: None,
13146                    },
13147                ]),
13148                active_parameter: None,
13149            },
13150        ],
13151        active_signature: Some(1),
13152        active_parameter: Some(0),
13153    };
13154    handle_signature_help_request(&mut cx, mocked_response).await;
13155
13156    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13157        .await;
13158
13159    // Verify we have multiple signatures and the right one is selected
13160    cx.editor(|editor, _, _| {
13161        let popover = editor.signature_help_state.popover().cloned().unwrap();
13162        assert_eq!(popover.signatures.len(), 3);
13163        // active_signature was 1, so that should be the current
13164        assert_eq!(popover.current_signature, 1);
13165        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13166        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13167        assert_eq!(
13168            popover.signatures[2].label,
13169            "fn overloaded(x: i32, y: i32, z: i32)"
13170        );
13171    });
13172
13173    // Test navigation functionality
13174    cx.update_editor(|editor, window, cx| {
13175        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13176    });
13177
13178    cx.editor(|editor, _, _| {
13179        let popover = editor.signature_help_state.popover().cloned().unwrap();
13180        assert_eq!(popover.current_signature, 2);
13181    });
13182
13183    // Test wrap around
13184    cx.update_editor(|editor, window, cx| {
13185        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13186    });
13187
13188    cx.editor(|editor, _, _| {
13189        let popover = editor.signature_help_state.popover().cloned().unwrap();
13190        assert_eq!(popover.current_signature, 0);
13191    });
13192
13193    // Test previous navigation
13194    cx.update_editor(|editor, window, cx| {
13195        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13196    });
13197
13198    cx.editor(|editor, _, _| {
13199        let popover = editor.signature_help_state.popover().cloned().unwrap();
13200        assert_eq!(popover.current_signature, 2);
13201    });
13202}
13203
13204#[gpui::test]
13205async fn test_completion_mode(cx: &mut TestAppContext) {
13206    init_test(cx, |_| {});
13207    let mut cx = EditorLspTestContext::new_rust(
13208        lsp::ServerCapabilities {
13209            completion_provider: Some(lsp::CompletionOptions {
13210                resolve_provider: Some(true),
13211                ..Default::default()
13212            }),
13213            ..Default::default()
13214        },
13215        cx,
13216    )
13217    .await;
13218
13219    struct Run {
13220        run_description: &'static str,
13221        initial_state: String,
13222        buffer_marked_text: String,
13223        completion_label: &'static str,
13224        completion_text: &'static str,
13225        expected_with_insert_mode: String,
13226        expected_with_replace_mode: String,
13227        expected_with_replace_subsequence_mode: String,
13228        expected_with_replace_suffix_mode: String,
13229    }
13230
13231    let runs = [
13232        Run {
13233            run_description: "Start of word matches completion text",
13234            initial_state: "before ediˇ after".into(),
13235            buffer_marked_text: "before <edi|> after".into(),
13236            completion_label: "editor",
13237            completion_text: "editor",
13238            expected_with_insert_mode: "before editorˇ after".into(),
13239            expected_with_replace_mode: "before editorˇ after".into(),
13240            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13241            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13242        },
13243        Run {
13244            run_description: "Accept same text at the middle of the word",
13245            initial_state: "before ediˇtor after".into(),
13246            buffer_marked_text: "before <edi|tor> after".into(),
13247            completion_label: "editor",
13248            completion_text: "editor",
13249            expected_with_insert_mode: "before editorˇtor after".into(),
13250            expected_with_replace_mode: "before editorˇ after".into(),
13251            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13252            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13253        },
13254        Run {
13255            run_description: "End of word matches completion text -- cursor at end",
13256            initial_state: "before torˇ after".into(),
13257            buffer_marked_text: "before <tor|> after".into(),
13258            completion_label: "editor",
13259            completion_text: "editor",
13260            expected_with_insert_mode: "before editorˇ after".into(),
13261            expected_with_replace_mode: "before editorˇ after".into(),
13262            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13263            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13264        },
13265        Run {
13266            run_description: "End of word matches completion text -- cursor at start",
13267            initial_state: "before ˇtor after".into(),
13268            buffer_marked_text: "before <|tor> after".into(),
13269            completion_label: "editor",
13270            completion_text: "editor",
13271            expected_with_insert_mode: "before editorˇtor after".into(),
13272            expected_with_replace_mode: "before editorˇ after".into(),
13273            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13274            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13275        },
13276        Run {
13277            run_description: "Prepend text containing whitespace",
13278            initial_state: "pˇfield: bool".into(),
13279            buffer_marked_text: "<p|field>: bool".into(),
13280            completion_label: "pub ",
13281            completion_text: "pub ",
13282            expected_with_insert_mode: "pub ˇfield: bool".into(),
13283            expected_with_replace_mode: "pub ˇ: bool".into(),
13284            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13285            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13286        },
13287        Run {
13288            run_description: "Add element to start of list",
13289            initial_state: "[element_ˇelement_2]".into(),
13290            buffer_marked_text: "[<element_|element_2>]".into(),
13291            completion_label: "element_1",
13292            completion_text: "element_1",
13293            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13294            expected_with_replace_mode: "[element_1ˇ]".into(),
13295            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13296            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13297        },
13298        Run {
13299            run_description: "Add element to start of list -- first and second elements are equal",
13300            initial_state: "[elˇelement]".into(),
13301            buffer_marked_text: "[<el|element>]".into(),
13302            completion_label: "element",
13303            completion_text: "element",
13304            expected_with_insert_mode: "[elementˇelement]".into(),
13305            expected_with_replace_mode: "[elementˇ]".into(),
13306            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13307            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13308        },
13309        Run {
13310            run_description: "Ends with matching suffix",
13311            initial_state: "SubˇError".into(),
13312            buffer_marked_text: "<Sub|Error>".into(),
13313            completion_label: "SubscriptionError",
13314            completion_text: "SubscriptionError",
13315            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13316            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13317            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13318            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13319        },
13320        Run {
13321            run_description: "Suffix is a subsequence -- contiguous",
13322            initial_state: "SubˇErr".into(),
13323            buffer_marked_text: "<Sub|Err>".into(),
13324            completion_label: "SubscriptionError",
13325            completion_text: "SubscriptionError",
13326            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13327            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13328            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13329            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13330        },
13331        Run {
13332            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13333            initial_state: "Suˇscrirr".into(),
13334            buffer_marked_text: "<Su|scrirr>".into(),
13335            completion_label: "SubscriptionError",
13336            completion_text: "SubscriptionError",
13337            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13338            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13339            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13340            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13341        },
13342        Run {
13343            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13344            initial_state: "foo(indˇix)".into(),
13345            buffer_marked_text: "foo(<ind|ix>)".into(),
13346            completion_label: "node_index",
13347            completion_text: "node_index",
13348            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13349            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13350            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13351            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13352        },
13353        Run {
13354            run_description: "Replace range ends before cursor - should extend to cursor",
13355            initial_state: "before editˇo after".into(),
13356            buffer_marked_text: "before <{ed}>it|o after".into(),
13357            completion_label: "editor",
13358            completion_text: "editor",
13359            expected_with_insert_mode: "before editorˇo after".into(),
13360            expected_with_replace_mode: "before editorˇo after".into(),
13361            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13362            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13363        },
13364        Run {
13365            run_description: "Uses label for suffix matching",
13366            initial_state: "before ediˇtor after".into(),
13367            buffer_marked_text: "before <edi|tor> after".into(),
13368            completion_label: "editor",
13369            completion_text: "editor()",
13370            expected_with_insert_mode: "before editor()ˇtor after".into(),
13371            expected_with_replace_mode: "before editor()ˇ after".into(),
13372            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13373            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13374        },
13375        Run {
13376            run_description: "Case insensitive subsequence and suffix matching",
13377            initial_state: "before EDiˇtoR after".into(),
13378            buffer_marked_text: "before <EDi|toR> after".into(),
13379            completion_label: "editor",
13380            completion_text: "editor",
13381            expected_with_insert_mode: "before editorˇtoR after".into(),
13382            expected_with_replace_mode: "before editorˇ after".into(),
13383            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13384            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13385        },
13386    ];
13387
13388    for run in runs {
13389        let run_variations = [
13390            (LspInsertMode::Insert, run.expected_with_insert_mode),
13391            (LspInsertMode::Replace, run.expected_with_replace_mode),
13392            (
13393                LspInsertMode::ReplaceSubsequence,
13394                run.expected_with_replace_subsequence_mode,
13395            ),
13396            (
13397                LspInsertMode::ReplaceSuffix,
13398                run.expected_with_replace_suffix_mode,
13399            ),
13400        ];
13401
13402        for (lsp_insert_mode, expected_text) in run_variations {
13403            eprintln!(
13404                "run = {:?}, mode = {lsp_insert_mode:.?}",
13405                run.run_description,
13406            );
13407
13408            update_test_language_settings(&mut cx, |settings| {
13409                settings.defaults.completions = Some(CompletionSettingsContent {
13410                    lsp_insert_mode: Some(lsp_insert_mode),
13411                    words: Some(WordsCompletionMode::Disabled),
13412                    words_min_length: Some(0),
13413                    ..Default::default()
13414                });
13415            });
13416
13417            cx.set_state(&run.initial_state);
13418            cx.update_editor(|editor, window, cx| {
13419                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13420            });
13421
13422            let counter = Arc::new(AtomicUsize::new(0));
13423            handle_completion_request_with_insert_and_replace(
13424                &mut cx,
13425                &run.buffer_marked_text,
13426                vec![(run.completion_label, run.completion_text)],
13427                counter.clone(),
13428            )
13429            .await;
13430            cx.condition(|editor, _| editor.context_menu_visible())
13431                .await;
13432            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13433
13434            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13435                editor
13436                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13437                    .unwrap()
13438            });
13439            cx.assert_editor_state(&expected_text);
13440            handle_resolve_completion_request(&mut cx, None).await;
13441            apply_additional_edits.await.unwrap();
13442        }
13443    }
13444}
13445
13446#[gpui::test]
13447async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13448    init_test(cx, |_| {});
13449    let mut cx = EditorLspTestContext::new_rust(
13450        lsp::ServerCapabilities {
13451            completion_provider: Some(lsp::CompletionOptions {
13452                resolve_provider: Some(true),
13453                ..Default::default()
13454            }),
13455            ..Default::default()
13456        },
13457        cx,
13458    )
13459    .await;
13460
13461    let initial_state = "SubˇError";
13462    let buffer_marked_text = "<Sub|Error>";
13463    let completion_text = "SubscriptionError";
13464    let expected_with_insert_mode = "SubscriptionErrorˇError";
13465    let expected_with_replace_mode = "SubscriptionErrorˇ";
13466
13467    update_test_language_settings(&mut cx, |settings| {
13468        settings.defaults.completions = Some(CompletionSettingsContent {
13469            words: Some(WordsCompletionMode::Disabled),
13470            words_min_length: Some(0),
13471            // set the opposite here to ensure that the action is overriding the default behavior
13472            lsp_insert_mode: Some(LspInsertMode::Insert),
13473            ..Default::default()
13474        });
13475    });
13476
13477    cx.set_state(initial_state);
13478    cx.update_editor(|editor, window, cx| {
13479        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13480    });
13481
13482    let counter = Arc::new(AtomicUsize::new(0));
13483    handle_completion_request_with_insert_and_replace(
13484        &mut cx,
13485        buffer_marked_text,
13486        vec![(completion_text, completion_text)],
13487        counter.clone(),
13488    )
13489    .await;
13490    cx.condition(|editor, _| editor.context_menu_visible())
13491        .await;
13492    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13493
13494    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13495        editor
13496            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13497            .unwrap()
13498    });
13499    cx.assert_editor_state(expected_with_replace_mode);
13500    handle_resolve_completion_request(&mut cx, None).await;
13501    apply_additional_edits.await.unwrap();
13502
13503    update_test_language_settings(&mut cx, |settings| {
13504        settings.defaults.completions = Some(CompletionSettingsContent {
13505            words: Some(WordsCompletionMode::Disabled),
13506            words_min_length: Some(0),
13507            // set the opposite here to ensure that the action is overriding the default behavior
13508            lsp_insert_mode: Some(LspInsertMode::Replace),
13509            ..Default::default()
13510        });
13511    });
13512
13513    cx.set_state(initial_state);
13514    cx.update_editor(|editor, window, cx| {
13515        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13516    });
13517    handle_completion_request_with_insert_and_replace(
13518        &mut cx,
13519        buffer_marked_text,
13520        vec![(completion_text, completion_text)],
13521        counter.clone(),
13522    )
13523    .await;
13524    cx.condition(|editor, _| editor.context_menu_visible())
13525        .await;
13526    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13527
13528    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13529        editor
13530            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13531            .unwrap()
13532    });
13533    cx.assert_editor_state(expected_with_insert_mode);
13534    handle_resolve_completion_request(&mut cx, None).await;
13535    apply_additional_edits.await.unwrap();
13536}
13537
13538#[gpui::test]
13539async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13540    init_test(cx, |_| {});
13541    let mut cx = EditorLspTestContext::new_rust(
13542        lsp::ServerCapabilities {
13543            completion_provider: Some(lsp::CompletionOptions {
13544                resolve_provider: Some(true),
13545                ..Default::default()
13546            }),
13547            ..Default::default()
13548        },
13549        cx,
13550    )
13551    .await;
13552
13553    // scenario: surrounding text matches completion text
13554    let completion_text = "to_offset";
13555    let initial_state = indoc! {"
13556        1. buf.to_offˇsuffix
13557        2. buf.to_offˇsuf
13558        3. buf.to_offˇfix
13559        4. buf.to_offˇ
13560        5. into_offˇensive
13561        6. ˇsuffix
13562        7. let ˇ //
13563        8. aaˇzz
13564        9. buf.to_off«zzzzzˇ»suffix
13565        10. buf.«ˇzzzzz»suffix
13566        11. to_off«ˇzzzzz»
13567
13568        buf.to_offˇsuffix  // newest cursor
13569    "};
13570    let completion_marked_buffer = indoc! {"
13571        1. buf.to_offsuffix
13572        2. buf.to_offsuf
13573        3. buf.to_offfix
13574        4. buf.to_off
13575        5. into_offensive
13576        6. suffix
13577        7. let  //
13578        8. aazz
13579        9. buf.to_offzzzzzsuffix
13580        10. buf.zzzzzsuffix
13581        11. to_offzzzzz
13582
13583        buf.<to_off|suffix>  // newest cursor
13584    "};
13585    let expected = indoc! {"
13586        1. buf.to_offsetˇ
13587        2. buf.to_offsetˇsuf
13588        3. buf.to_offsetˇfix
13589        4. buf.to_offsetˇ
13590        5. into_offsetˇensive
13591        6. to_offsetˇsuffix
13592        7. let to_offsetˇ //
13593        8. aato_offsetˇzz
13594        9. buf.to_offsetˇ
13595        10. buf.to_offsetˇsuffix
13596        11. to_offsetˇ
13597
13598        buf.to_offsetˇ  // newest cursor
13599    "};
13600    cx.set_state(initial_state);
13601    cx.update_editor(|editor, window, cx| {
13602        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13603    });
13604    handle_completion_request_with_insert_and_replace(
13605        &mut cx,
13606        completion_marked_buffer,
13607        vec![(completion_text, completion_text)],
13608        Arc::new(AtomicUsize::new(0)),
13609    )
13610    .await;
13611    cx.condition(|editor, _| editor.context_menu_visible())
13612        .await;
13613    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13614        editor
13615            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13616            .unwrap()
13617    });
13618    cx.assert_editor_state(expected);
13619    handle_resolve_completion_request(&mut cx, None).await;
13620    apply_additional_edits.await.unwrap();
13621
13622    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13623    let completion_text = "foo_and_bar";
13624    let initial_state = indoc! {"
13625        1. ooanbˇ
13626        2. zooanbˇ
13627        3. ooanbˇz
13628        4. zooanbˇz
13629        5. ooanˇ
13630        6. oanbˇ
13631
13632        ooanbˇ
13633    "};
13634    let completion_marked_buffer = indoc! {"
13635        1. ooanb
13636        2. zooanb
13637        3. ooanbz
13638        4. zooanbz
13639        5. ooan
13640        6. oanb
13641
13642        <ooanb|>
13643    "};
13644    let expected = indoc! {"
13645        1. foo_and_barˇ
13646        2. zfoo_and_barˇ
13647        3. foo_and_barˇz
13648        4. zfoo_and_barˇz
13649        5. ooanfoo_and_barˇ
13650        6. oanbfoo_and_barˇ
13651
13652        foo_and_barˇ
13653    "};
13654    cx.set_state(initial_state);
13655    cx.update_editor(|editor, window, cx| {
13656        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13657    });
13658    handle_completion_request_with_insert_and_replace(
13659        &mut cx,
13660        completion_marked_buffer,
13661        vec![(completion_text, completion_text)],
13662        Arc::new(AtomicUsize::new(0)),
13663    )
13664    .await;
13665    cx.condition(|editor, _| editor.context_menu_visible())
13666        .await;
13667    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13668        editor
13669            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13670            .unwrap()
13671    });
13672    cx.assert_editor_state(expected);
13673    handle_resolve_completion_request(&mut cx, None).await;
13674    apply_additional_edits.await.unwrap();
13675
13676    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13677    // (expects the same as if it was inserted at the end)
13678    let completion_text = "foo_and_bar";
13679    let initial_state = indoc! {"
13680        1. ooˇanb
13681        2. zooˇanb
13682        3. ooˇanbz
13683        4. zooˇanbz
13684
13685        ooˇanb
13686    "};
13687    let completion_marked_buffer = indoc! {"
13688        1. ooanb
13689        2. zooanb
13690        3. ooanbz
13691        4. zooanbz
13692
13693        <oo|anb>
13694    "};
13695    let expected = indoc! {"
13696        1. foo_and_barˇ
13697        2. zfoo_and_barˇ
13698        3. foo_and_barˇz
13699        4. zfoo_and_barˇz
13700
13701        foo_and_barˇ
13702    "};
13703    cx.set_state(initial_state);
13704    cx.update_editor(|editor, window, cx| {
13705        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13706    });
13707    handle_completion_request_with_insert_and_replace(
13708        &mut cx,
13709        completion_marked_buffer,
13710        vec![(completion_text, completion_text)],
13711        Arc::new(AtomicUsize::new(0)),
13712    )
13713    .await;
13714    cx.condition(|editor, _| editor.context_menu_visible())
13715        .await;
13716    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13717        editor
13718            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13719            .unwrap()
13720    });
13721    cx.assert_editor_state(expected);
13722    handle_resolve_completion_request(&mut cx, None).await;
13723    apply_additional_edits.await.unwrap();
13724}
13725
13726// This used to crash
13727#[gpui::test]
13728async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13729    init_test(cx, |_| {});
13730
13731    let buffer_text = indoc! {"
13732        fn main() {
13733            10.satu;
13734
13735            //
13736            // separate cursors so they open in different excerpts (manually reproducible)
13737            //
13738
13739            10.satu20;
13740        }
13741    "};
13742    let multibuffer_text_with_selections = indoc! {"
13743        fn main() {
13744            10.satuˇ;
13745
13746            //
13747
13748            //
13749
13750            10.satuˇ20;
13751        }
13752    "};
13753    let expected_multibuffer = indoc! {"
13754        fn main() {
13755            10.saturating_sub()ˇ;
13756
13757            //
13758
13759            //
13760
13761            10.saturating_sub()ˇ;
13762        }
13763    "};
13764
13765    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13766    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13767
13768    let fs = FakeFs::new(cx.executor());
13769    fs.insert_tree(
13770        path!("/a"),
13771        json!({
13772            "main.rs": buffer_text,
13773        }),
13774    )
13775    .await;
13776
13777    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13778    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13779    language_registry.add(rust_lang());
13780    let mut fake_servers = language_registry.register_fake_lsp(
13781        "Rust",
13782        FakeLspAdapter {
13783            capabilities: lsp::ServerCapabilities {
13784                completion_provider: Some(lsp::CompletionOptions {
13785                    resolve_provider: None,
13786                    ..lsp::CompletionOptions::default()
13787                }),
13788                ..lsp::ServerCapabilities::default()
13789            },
13790            ..FakeLspAdapter::default()
13791        },
13792    );
13793    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13794    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13795    let buffer = project
13796        .update(cx, |project, cx| {
13797            project.open_local_buffer(path!("/a/main.rs"), cx)
13798        })
13799        .await
13800        .unwrap();
13801
13802    let multi_buffer = cx.new(|cx| {
13803        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13804        multi_buffer.push_excerpts(
13805            buffer.clone(),
13806            [ExcerptRange::new(0..first_excerpt_end)],
13807            cx,
13808        );
13809        multi_buffer.push_excerpts(
13810            buffer.clone(),
13811            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13812            cx,
13813        );
13814        multi_buffer
13815    });
13816
13817    let editor = workspace
13818        .update(cx, |_, window, cx| {
13819            cx.new(|cx| {
13820                Editor::new(
13821                    EditorMode::Full {
13822                        scale_ui_elements_with_buffer_font_size: false,
13823                        show_active_line_background: false,
13824                        sized_by_content: false,
13825                    },
13826                    multi_buffer.clone(),
13827                    Some(project.clone()),
13828                    window,
13829                    cx,
13830                )
13831            })
13832        })
13833        .unwrap();
13834
13835    let pane = workspace
13836        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13837        .unwrap();
13838    pane.update_in(cx, |pane, window, cx| {
13839        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13840    });
13841
13842    let fake_server = fake_servers.next().await.unwrap();
13843
13844    editor.update_in(cx, |editor, window, cx| {
13845        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13846            s.select_ranges([
13847                Point::new(1, 11)..Point::new(1, 11),
13848                Point::new(7, 11)..Point::new(7, 11),
13849            ])
13850        });
13851
13852        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13853    });
13854
13855    editor.update_in(cx, |editor, window, cx| {
13856        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13857    });
13858
13859    fake_server
13860        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13861            let completion_item = lsp::CompletionItem {
13862                label: "saturating_sub()".into(),
13863                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13864                    lsp::InsertReplaceEdit {
13865                        new_text: "saturating_sub()".to_owned(),
13866                        insert: lsp::Range::new(
13867                            lsp::Position::new(7, 7),
13868                            lsp::Position::new(7, 11),
13869                        ),
13870                        replace: lsp::Range::new(
13871                            lsp::Position::new(7, 7),
13872                            lsp::Position::new(7, 13),
13873                        ),
13874                    },
13875                )),
13876                ..lsp::CompletionItem::default()
13877            };
13878
13879            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13880        })
13881        .next()
13882        .await
13883        .unwrap();
13884
13885    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13886        .await;
13887
13888    editor
13889        .update_in(cx, |editor, window, cx| {
13890            editor
13891                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13892                .unwrap()
13893        })
13894        .await
13895        .unwrap();
13896
13897    editor.update(cx, |editor, cx| {
13898        assert_text_with_selections(editor, expected_multibuffer, cx);
13899    })
13900}
13901
13902#[gpui::test]
13903async fn test_completion(cx: &mut TestAppContext) {
13904    init_test(cx, |_| {});
13905
13906    let mut cx = EditorLspTestContext::new_rust(
13907        lsp::ServerCapabilities {
13908            completion_provider: Some(lsp::CompletionOptions {
13909                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13910                resolve_provider: Some(true),
13911                ..Default::default()
13912            }),
13913            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13914            ..Default::default()
13915        },
13916        cx,
13917    )
13918    .await;
13919    let counter = Arc::new(AtomicUsize::new(0));
13920
13921    cx.set_state(indoc! {"
13922        oneˇ
13923        two
13924        three
13925    "});
13926    cx.simulate_keystroke(".");
13927    handle_completion_request(
13928        indoc! {"
13929            one.|<>
13930            two
13931            three
13932        "},
13933        vec!["first_completion", "second_completion"],
13934        true,
13935        counter.clone(),
13936        &mut cx,
13937    )
13938    .await;
13939    cx.condition(|editor, _| editor.context_menu_visible())
13940        .await;
13941    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13942
13943    let _handler = handle_signature_help_request(
13944        &mut cx,
13945        lsp::SignatureHelp {
13946            signatures: vec![lsp::SignatureInformation {
13947                label: "test signature".to_string(),
13948                documentation: None,
13949                parameters: Some(vec![lsp::ParameterInformation {
13950                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
13951                    documentation: None,
13952                }]),
13953                active_parameter: None,
13954            }],
13955            active_signature: None,
13956            active_parameter: None,
13957        },
13958    );
13959    cx.update_editor(|editor, window, cx| {
13960        assert!(
13961            !editor.signature_help_state.is_shown(),
13962            "No signature help was called for"
13963        );
13964        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13965    });
13966    cx.run_until_parked();
13967    cx.update_editor(|editor, _, _| {
13968        assert!(
13969            !editor.signature_help_state.is_shown(),
13970            "No signature help should be shown when completions menu is open"
13971        );
13972    });
13973
13974    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13975        editor.context_menu_next(&Default::default(), window, cx);
13976        editor
13977            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13978            .unwrap()
13979    });
13980    cx.assert_editor_state(indoc! {"
13981        one.second_completionˇ
13982        two
13983        three
13984    "});
13985
13986    handle_resolve_completion_request(
13987        &mut cx,
13988        Some(vec![
13989            (
13990                //This overlaps with the primary completion edit which is
13991                //misbehavior from the LSP spec, test that we filter it out
13992                indoc! {"
13993                    one.second_ˇcompletion
13994                    two
13995                    threeˇ
13996                "},
13997                "overlapping additional edit",
13998            ),
13999            (
14000                indoc! {"
14001                    one.second_completion
14002                    two
14003                    threeˇ
14004                "},
14005                "\nadditional edit",
14006            ),
14007        ]),
14008    )
14009    .await;
14010    apply_additional_edits.await.unwrap();
14011    cx.assert_editor_state(indoc! {"
14012        one.second_completionˇ
14013        two
14014        three
14015        additional edit
14016    "});
14017
14018    cx.set_state(indoc! {"
14019        one.second_completion
14020        twoˇ
14021        threeˇ
14022        additional edit
14023    "});
14024    cx.simulate_keystroke(" ");
14025    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14026    cx.simulate_keystroke("s");
14027    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14028
14029    cx.assert_editor_state(indoc! {"
14030        one.second_completion
14031        two sˇ
14032        three sˇ
14033        additional edit
14034    "});
14035    handle_completion_request(
14036        indoc! {"
14037            one.second_completion
14038            two s
14039            three <s|>
14040            additional edit
14041        "},
14042        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14043        true,
14044        counter.clone(),
14045        &mut cx,
14046    )
14047    .await;
14048    cx.condition(|editor, _| editor.context_menu_visible())
14049        .await;
14050    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14051
14052    cx.simulate_keystroke("i");
14053
14054    handle_completion_request(
14055        indoc! {"
14056            one.second_completion
14057            two si
14058            three <si|>
14059            additional edit
14060        "},
14061        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14062        true,
14063        counter.clone(),
14064        &mut cx,
14065    )
14066    .await;
14067    cx.condition(|editor, _| editor.context_menu_visible())
14068        .await;
14069    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14070
14071    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14072        editor
14073            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14074            .unwrap()
14075    });
14076    cx.assert_editor_state(indoc! {"
14077        one.second_completion
14078        two sixth_completionˇ
14079        three sixth_completionˇ
14080        additional edit
14081    "});
14082
14083    apply_additional_edits.await.unwrap();
14084
14085    update_test_language_settings(&mut cx, |settings| {
14086        settings.defaults.show_completions_on_input = Some(false);
14087    });
14088    cx.set_state("editorˇ");
14089    cx.simulate_keystroke(".");
14090    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14091    cx.simulate_keystrokes("c l o");
14092    cx.assert_editor_state("editor.cloˇ");
14093    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14094    cx.update_editor(|editor, window, cx| {
14095        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14096    });
14097    handle_completion_request(
14098        "editor.<clo|>",
14099        vec!["close", "clobber"],
14100        true,
14101        counter.clone(),
14102        &mut cx,
14103    )
14104    .await;
14105    cx.condition(|editor, _| editor.context_menu_visible())
14106        .await;
14107    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14108
14109    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14110        editor
14111            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14112            .unwrap()
14113    });
14114    cx.assert_editor_state("editor.clobberˇ");
14115    handle_resolve_completion_request(&mut cx, None).await;
14116    apply_additional_edits.await.unwrap();
14117}
14118
14119#[gpui::test]
14120async fn test_completion_reuse(cx: &mut TestAppContext) {
14121    init_test(cx, |_| {});
14122
14123    let mut cx = EditorLspTestContext::new_rust(
14124        lsp::ServerCapabilities {
14125            completion_provider: Some(lsp::CompletionOptions {
14126                trigger_characters: Some(vec![".".to_string()]),
14127                ..Default::default()
14128            }),
14129            ..Default::default()
14130        },
14131        cx,
14132    )
14133    .await;
14134
14135    let counter = Arc::new(AtomicUsize::new(0));
14136    cx.set_state("objˇ");
14137    cx.simulate_keystroke(".");
14138
14139    // Initial completion request returns complete results
14140    let is_incomplete = false;
14141    handle_completion_request(
14142        "obj.|<>",
14143        vec!["a", "ab", "abc"],
14144        is_incomplete,
14145        counter.clone(),
14146        &mut cx,
14147    )
14148    .await;
14149    cx.run_until_parked();
14150    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14151    cx.assert_editor_state("obj.ˇ");
14152    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14153
14154    // Type "a" - filters existing completions
14155    cx.simulate_keystroke("a");
14156    cx.run_until_parked();
14157    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14158    cx.assert_editor_state("obj.aˇ");
14159    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14160
14161    // Type "b" - filters existing completions
14162    cx.simulate_keystroke("b");
14163    cx.run_until_parked();
14164    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14165    cx.assert_editor_state("obj.abˇ");
14166    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14167
14168    // Type "c" - filters existing completions
14169    cx.simulate_keystroke("c");
14170    cx.run_until_parked();
14171    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14172    cx.assert_editor_state("obj.abcˇ");
14173    check_displayed_completions(vec!["abc"], &mut cx);
14174
14175    // Backspace to delete "c" - filters existing completions
14176    cx.update_editor(|editor, window, cx| {
14177        editor.backspace(&Backspace, window, cx);
14178    });
14179    cx.run_until_parked();
14180    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14181    cx.assert_editor_state("obj.abˇ");
14182    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14183
14184    // Moving cursor to the left dismisses menu.
14185    cx.update_editor(|editor, window, cx| {
14186        editor.move_left(&MoveLeft, window, cx);
14187    });
14188    cx.run_until_parked();
14189    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14190    cx.assert_editor_state("obj.aˇb");
14191    cx.update_editor(|editor, _, _| {
14192        assert_eq!(editor.context_menu_visible(), false);
14193    });
14194
14195    // Type "b" - new request
14196    cx.simulate_keystroke("b");
14197    let is_incomplete = false;
14198    handle_completion_request(
14199        "obj.<ab|>a",
14200        vec!["ab", "abc"],
14201        is_incomplete,
14202        counter.clone(),
14203        &mut cx,
14204    )
14205    .await;
14206    cx.run_until_parked();
14207    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14208    cx.assert_editor_state("obj.abˇb");
14209    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14210
14211    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14212    cx.update_editor(|editor, window, cx| {
14213        editor.backspace(&Backspace, window, cx);
14214    });
14215    let is_incomplete = false;
14216    handle_completion_request(
14217        "obj.<a|>b",
14218        vec!["a", "ab", "abc"],
14219        is_incomplete,
14220        counter.clone(),
14221        &mut cx,
14222    )
14223    .await;
14224    cx.run_until_parked();
14225    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14226    cx.assert_editor_state("obj.aˇb");
14227    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14228
14229    // Backspace to delete "a" - dismisses menu.
14230    cx.update_editor(|editor, window, cx| {
14231        editor.backspace(&Backspace, window, cx);
14232    });
14233    cx.run_until_parked();
14234    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14235    cx.assert_editor_state("obj.ˇb");
14236    cx.update_editor(|editor, _, _| {
14237        assert_eq!(editor.context_menu_visible(), false);
14238    });
14239}
14240
14241#[gpui::test]
14242async fn test_word_completion(cx: &mut TestAppContext) {
14243    let lsp_fetch_timeout_ms = 10;
14244    init_test(cx, |language_settings| {
14245        language_settings.defaults.completions = Some(CompletionSettingsContent {
14246            words_min_length: Some(0),
14247            lsp_fetch_timeout_ms: Some(10),
14248            lsp_insert_mode: Some(LspInsertMode::Insert),
14249            ..Default::default()
14250        });
14251    });
14252
14253    let mut cx = EditorLspTestContext::new_rust(
14254        lsp::ServerCapabilities {
14255            completion_provider: Some(lsp::CompletionOptions {
14256                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14257                ..lsp::CompletionOptions::default()
14258            }),
14259            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14260            ..lsp::ServerCapabilities::default()
14261        },
14262        cx,
14263    )
14264    .await;
14265
14266    let throttle_completions = Arc::new(AtomicBool::new(false));
14267
14268    let lsp_throttle_completions = throttle_completions.clone();
14269    let _completion_requests_handler =
14270        cx.lsp
14271            .server
14272            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14273                let lsp_throttle_completions = lsp_throttle_completions.clone();
14274                let cx = cx.clone();
14275                async move {
14276                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14277                        cx.background_executor()
14278                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14279                            .await;
14280                    }
14281                    Ok(Some(lsp::CompletionResponse::Array(vec![
14282                        lsp::CompletionItem {
14283                            label: "first".into(),
14284                            ..lsp::CompletionItem::default()
14285                        },
14286                        lsp::CompletionItem {
14287                            label: "last".into(),
14288                            ..lsp::CompletionItem::default()
14289                        },
14290                    ])))
14291                }
14292            });
14293
14294    cx.set_state(indoc! {"
14295        oneˇ
14296        two
14297        three
14298    "});
14299    cx.simulate_keystroke(".");
14300    cx.executor().run_until_parked();
14301    cx.condition(|editor, _| editor.context_menu_visible())
14302        .await;
14303    cx.update_editor(|editor, window, cx| {
14304        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14305        {
14306            assert_eq!(
14307                completion_menu_entries(menu),
14308                &["first", "last"],
14309                "When LSP server is fast to reply, no fallback word completions are used"
14310            );
14311        } else {
14312            panic!("expected completion menu to be open");
14313        }
14314        editor.cancel(&Cancel, window, cx);
14315    });
14316    cx.executor().run_until_parked();
14317    cx.condition(|editor, _| !editor.context_menu_visible())
14318        .await;
14319
14320    throttle_completions.store(true, atomic::Ordering::Release);
14321    cx.simulate_keystroke(".");
14322    cx.executor()
14323        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14324    cx.executor().run_until_parked();
14325    cx.condition(|editor, _| editor.context_menu_visible())
14326        .await;
14327    cx.update_editor(|editor, _, _| {
14328        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14329        {
14330            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14331                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14332        } else {
14333            panic!("expected completion menu to be open");
14334        }
14335    });
14336}
14337
14338#[gpui::test]
14339async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14340    init_test(cx, |language_settings| {
14341        language_settings.defaults.completions = Some(CompletionSettingsContent {
14342            words: Some(WordsCompletionMode::Enabled),
14343            words_min_length: Some(0),
14344            lsp_insert_mode: Some(LspInsertMode::Insert),
14345            ..Default::default()
14346        });
14347    });
14348
14349    let mut cx = EditorLspTestContext::new_rust(
14350        lsp::ServerCapabilities {
14351            completion_provider: Some(lsp::CompletionOptions {
14352                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14353                ..lsp::CompletionOptions::default()
14354            }),
14355            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14356            ..lsp::ServerCapabilities::default()
14357        },
14358        cx,
14359    )
14360    .await;
14361
14362    let _completion_requests_handler =
14363        cx.lsp
14364            .server
14365            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14366                Ok(Some(lsp::CompletionResponse::Array(vec![
14367                    lsp::CompletionItem {
14368                        label: "first".into(),
14369                        ..lsp::CompletionItem::default()
14370                    },
14371                    lsp::CompletionItem {
14372                        label: "last".into(),
14373                        ..lsp::CompletionItem::default()
14374                    },
14375                ])))
14376            });
14377
14378    cx.set_state(indoc! {"ˇ
14379        first
14380        last
14381        second
14382    "});
14383    cx.simulate_keystroke(".");
14384    cx.executor().run_until_parked();
14385    cx.condition(|editor, _| editor.context_menu_visible())
14386        .await;
14387    cx.update_editor(|editor, _, _| {
14388        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14389        {
14390            assert_eq!(
14391                completion_menu_entries(menu),
14392                &["first", "last", "second"],
14393                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14394            );
14395        } else {
14396            panic!("expected completion menu to be open");
14397        }
14398    });
14399}
14400
14401#[gpui::test]
14402async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14403    init_test(cx, |language_settings| {
14404        language_settings.defaults.completions = Some(CompletionSettingsContent {
14405            words: Some(WordsCompletionMode::Disabled),
14406            words_min_length: Some(0),
14407            lsp_insert_mode: Some(LspInsertMode::Insert),
14408            ..Default::default()
14409        });
14410    });
14411
14412    let mut cx = EditorLspTestContext::new_rust(
14413        lsp::ServerCapabilities {
14414            completion_provider: Some(lsp::CompletionOptions {
14415                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14416                ..lsp::CompletionOptions::default()
14417            }),
14418            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14419            ..lsp::ServerCapabilities::default()
14420        },
14421        cx,
14422    )
14423    .await;
14424
14425    let _completion_requests_handler =
14426        cx.lsp
14427            .server
14428            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14429                panic!("LSP completions should not be queried when dealing with word completions")
14430            });
14431
14432    cx.set_state(indoc! {"ˇ
14433        first
14434        last
14435        second
14436    "});
14437    cx.update_editor(|editor, window, cx| {
14438        editor.show_word_completions(&ShowWordCompletions, window, cx);
14439    });
14440    cx.executor().run_until_parked();
14441    cx.condition(|editor, _| editor.context_menu_visible())
14442        .await;
14443    cx.update_editor(|editor, _, _| {
14444        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14445        {
14446            assert_eq!(
14447                completion_menu_entries(menu),
14448                &["first", "last", "second"],
14449                "`ShowWordCompletions` action should show word completions"
14450            );
14451        } else {
14452            panic!("expected completion menu to be open");
14453        }
14454    });
14455
14456    cx.simulate_keystroke("l");
14457    cx.executor().run_until_parked();
14458    cx.condition(|editor, _| editor.context_menu_visible())
14459        .await;
14460    cx.update_editor(|editor, _, _| {
14461        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14462        {
14463            assert_eq!(
14464                completion_menu_entries(menu),
14465                &["last"],
14466                "After showing word completions, further editing should filter them and not query the LSP"
14467            );
14468        } else {
14469            panic!("expected completion menu to be open");
14470        }
14471    });
14472}
14473
14474#[gpui::test]
14475async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14476    init_test(cx, |language_settings| {
14477        language_settings.defaults.completions = Some(CompletionSettingsContent {
14478            words_min_length: Some(0),
14479            lsp: Some(false),
14480            lsp_insert_mode: Some(LspInsertMode::Insert),
14481            ..Default::default()
14482        });
14483    });
14484
14485    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14486
14487    cx.set_state(indoc! {"ˇ
14488        0_usize
14489        let
14490        33
14491        4.5f32
14492    "});
14493    cx.update_editor(|editor, window, cx| {
14494        editor.show_completions(&ShowCompletions::default(), window, cx);
14495    });
14496    cx.executor().run_until_parked();
14497    cx.condition(|editor, _| editor.context_menu_visible())
14498        .await;
14499    cx.update_editor(|editor, window, cx| {
14500        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14501        {
14502            assert_eq!(
14503                completion_menu_entries(menu),
14504                &["let"],
14505                "With no digits in the completion query, no digits should be in the word completions"
14506            );
14507        } else {
14508            panic!("expected completion menu to be open");
14509        }
14510        editor.cancel(&Cancel, window, cx);
14511    });
14512
14513    cx.set_state(indoc! {"14514        0_usize
14515        let
14516        3
14517        33.35f32
14518    "});
14519    cx.update_editor(|editor, window, cx| {
14520        editor.show_completions(&ShowCompletions::default(), window, cx);
14521    });
14522    cx.executor().run_until_parked();
14523    cx.condition(|editor, _| editor.context_menu_visible())
14524        .await;
14525    cx.update_editor(|editor, _, _| {
14526        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14527        {
14528            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14529                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14530        } else {
14531            panic!("expected completion menu to be open");
14532        }
14533    });
14534}
14535
14536#[gpui::test]
14537async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14538    init_test(cx, |language_settings| {
14539        language_settings.defaults.completions = Some(CompletionSettingsContent {
14540            words: Some(WordsCompletionMode::Enabled),
14541            words_min_length: Some(3),
14542            lsp_insert_mode: Some(LspInsertMode::Insert),
14543            ..Default::default()
14544        });
14545    });
14546
14547    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14548    cx.set_state(indoc! {"ˇ
14549        wow
14550        wowen
14551        wowser
14552    "});
14553    cx.simulate_keystroke("w");
14554    cx.executor().run_until_parked();
14555    cx.update_editor(|editor, _, _| {
14556        if editor.context_menu.borrow_mut().is_some() {
14557            panic!(
14558                "expected completion menu to be hidden, as words completion threshold is not met"
14559            );
14560        }
14561    });
14562
14563    cx.update_editor(|editor, window, cx| {
14564        editor.show_word_completions(&ShowWordCompletions, window, cx);
14565    });
14566    cx.executor().run_until_parked();
14567    cx.update_editor(|editor, window, cx| {
14568        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14569        {
14570            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");
14571        } else {
14572            panic!("expected completion menu to be open after the word completions are called with an action");
14573        }
14574
14575        editor.cancel(&Cancel, window, cx);
14576    });
14577    cx.update_editor(|editor, _, _| {
14578        if editor.context_menu.borrow_mut().is_some() {
14579            panic!("expected completion menu to be hidden after canceling");
14580        }
14581    });
14582
14583    cx.simulate_keystroke("o");
14584    cx.executor().run_until_parked();
14585    cx.update_editor(|editor, _, _| {
14586        if editor.context_menu.borrow_mut().is_some() {
14587            panic!(
14588                "expected completion menu to be hidden, as words completion threshold is not met still"
14589            );
14590        }
14591    });
14592
14593    cx.simulate_keystroke("w");
14594    cx.executor().run_until_parked();
14595    cx.update_editor(|editor, _, _| {
14596        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14597        {
14598            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14599        } else {
14600            panic!("expected completion menu to be open after the word completions threshold is met");
14601        }
14602    });
14603}
14604
14605#[gpui::test]
14606async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14607    init_test(cx, |language_settings| {
14608        language_settings.defaults.completions = Some(CompletionSettingsContent {
14609            words: Some(WordsCompletionMode::Enabled),
14610            words_min_length: Some(0),
14611            lsp_insert_mode: Some(LspInsertMode::Insert),
14612            ..Default::default()
14613        });
14614    });
14615
14616    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14617    cx.update_editor(|editor, _, _| {
14618        editor.disable_word_completions();
14619    });
14620    cx.set_state(indoc! {"ˇ
14621        wow
14622        wowen
14623        wowser
14624    "});
14625    cx.simulate_keystroke("w");
14626    cx.executor().run_until_parked();
14627    cx.update_editor(|editor, _, _| {
14628        if editor.context_menu.borrow_mut().is_some() {
14629            panic!(
14630                "expected completion menu to be hidden, as words completion are disabled for this editor"
14631            );
14632        }
14633    });
14634
14635    cx.update_editor(|editor, window, cx| {
14636        editor.show_word_completions(&ShowWordCompletions, window, cx);
14637    });
14638    cx.executor().run_until_parked();
14639    cx.update_editor(|editor, _, _| {
14640        if editor.context_menu.borrow_mut().is_some() {
14641            panic!(
14642                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14643            );
14644        }
14645    });
14646}
14647
14648fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14649    let position = || lsp::Position {
14650        line: params.text_document_position.position.line,
14651        character: params.text_document_position.position.character,
14652    };
14653    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14654        range: lsp::Range {
14655            start: position(),
14656            end: position(),
14657        },
14658        new_text: text.to_string(),
14659    }))
14660}
14661
14662#[gpui::test]
14663async fn test_multiline_completion(cx: &mut TestAppContext) {
14664    init_test(cx, |_| {});
14665
14666    let fs = FakeFs::new(cx.executor());
14667    fs.insert_tree(
14668        path!("/a"),
14669        json!({
14670            "main.ts": "a",
14671        }),
14672    )
14673    .await;
14674
14675    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14676    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14677    let typescript_language = Arc::new(Language::new(
14678        LanguageConfig {
14679            name: "TypeScript".into(),
14680            matcher: LanguageMatcher {
14681                path_suffixes: vec!["ts".to_string()],
14682                ..LanguageMatcher::default()
14683            },
14684            line_comments: vec!["// ".into()],
14685            ..LanguageConfig::default()
14686        },
14687        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14688    ));
14689    language_registry.add(typescript_language.clone());
14690    let mut fake_servers = language_registry.register_fake_lsp(
14691        "TypeScript",
14692        FakeLspAdapter {
14693            capabilities: lsp::ServerCapabilities {
14694                completion_provider: Some(lsp::CompletionOptions {
14695                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14696                    ..lsp::CompletionOptions::default()
14697                }),
14698                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14699                ..lsp::ServerCapabilities::default()
14700            },
14701            // Emulate vtsls label generation
14702            label_for_completion: Some(Box::new(|item, _| {
14703                let text = if let Some(description) = item
14704                    .label_details
14705                    .as_ref()
14706                    .and_then(|label_details| label_details.description.as_ref())
14707                {
14708                    format!("{} {}", item.label, description)
14709                } else if let Some(detail) = &item.detail {
14710                    format!("{} {}", item.label, detail)
14711                } else {
14712                    item.label.clone()
14713                };
14714                let len = text.len();
14715                Some(language::CodeLabel {
14716                    text,
14717                    runs: Vec::new(),
14718                    filter_range: 0..len,
14719                })
14720            })),
14721            ..FakeLspAdapter::default()
14722        },
14723    );
14724    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14725    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14726    let worktree_id = workspace
14727        .update(cx, |workspace, _window, cx| {
14728            workspace.project().update(cx, |project, cx| {
14729                project.worktrees(cx).next().unwrap().read(cx).id()
14730            })
14731        })
14732        .unwrap();
14733    let _buffer = project
14734        .update(cx, |project, cx| {
14735            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14736        })
14737        .await
14738        .unwrap();
14739    let editor = workspace
14740        .update(cx, |workspace, window, cx| {
14741            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
14742        })
14743        .unwrap()
14744        .await
14745        .unwrap()
14746        .downcast::<Editor>()
14747        .unwrap();
14748    let fake_server = fake_servers.next().await.unwrap();
14749
14750    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14751    let multiline_label_2 = "a\nb\nc\n";
14752    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14753    let multiline_description = "d\ne\nf\n";
14754    let multiline_detail_2 = "g\nh\ni\n";
14755
14756    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14757        move |params, _| async move {
14758            Ok(Some(lsp::CompletionResponse::Array(vec![
14759                lsp::CompletionItem {
14760                    label: multiline_label.to_string(),
14761                    text_edit: gen_text_edit(&params, "new_text_1"),
14762                    ..lsp::CompletionItem::default()
14763                },
14764                lsp::CompletionItem {
14765                    label: "single line label 1".to_string(),
14766                    detail: Some(multiline_detail.to_string()),
14767                    text_edit: gen_text_edit(&params, "new_text_2"),
14768                    ..lsp::CompletionItem::default()
14769                },
14770                lsp::CompletionItem {
14771                    label: "single line label 2".to_string(),
14772                    label_details: Some(lsp::CompletionItemLabelDetails {
14773                        description: Some(multiline_description.to_string()),
14774                        detail: None,
14775                    }),
14776                    text_edit: gen_text_edit(&params, "new_text_2"),
14777                    ..lsp::CompletionItem::default()
14778                },
14779                lsp::CompletionItem {
14780                    label: multiline_label_2.to_string(),
14781                    detail: Some(multiline_detail_2.to_string()),
14782                    text_edit: gen_text_edit(&params, "new_text_3"),
14783                    ..lsp::CompletionItem::default()
14784                },
14785                lsp::CompletionItem {
14786                    label: "Label with many     spaces and \t but without newlines".to_string(),
14787                    detail: Some(
14788                        "Details with many     spaces and \t but without newlines".to_string(),
14789                    ),
14790                    text_edit: gen_text_edit(&params, "new_text_4"),
14791                    ..lsp::CompletionItem::default()
14792                },
14793            ])))
14794        },
14795    );
14796
14797    editor.update_in(cx, |editor, window, cx| {
14798        cx.focus_self(window);
14799        editor.move_to_end(&MoveToEnd, window, cx);
14800        editor.handle_input(".", window, cx);
14801    });
14802    cx.run_until_parked();
14803    completion_handle.next().await.unwrap();
14804
14805    editor.update(cx, |editor, _| {
14806        assert!(editor.context_menu_visible());
14807        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14808        {
14809            let completion_labels = menu
14810                .completions
14811                .borrow()
14812                .iter()
14813                .map(|c| c.label.text.clone())
14814                .collect::<Vec<_>>();
14815            assert_eq!(
14816                completion_labels,
14817                &[
14818                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14819                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14820                    "single line label 2 d e f ",
14821                    "a b c g h i ",
14822                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14823                ],
14824                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14825            );
14826
14827            for completion in menu
14828                .completions
14829                .borrow()
14830                .iter() {
14831                    assert_eq!(
14832                        completion.label.filter_range,
14833                        0..completion.label.text.len(),
14834                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14835                    );
14836                }
14837        } else {
14838            panic!("expected completion menu to be open");
14839        }
14840    });
14841}
14842
14843#[gpui::test]
14844async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14845    init_test(cx, |_| {});
14846    let mut cx = EditorLspTestContext::new_rust(
14847        lsp::ServerCapabilities {
14848            completion_provider: Some(lsp::CompletionOptions {
14849                trigger_characters: Some(vec![".".to_string()]),
14850                ..Default::default()
14851            }),
14852            ..Default::default()
14853        },
14854        cx,
14855    )
14856    .await;
14857    cx.lsp
14858        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14859            Ok(Some(lsp::CompletionResponse::Array(vec![
14860                lsp::CompletionItem {
14861                    label: "first".into(),
14862                    ..Default::default()
14863                },
14864                lsp::CompletionItem {
14865                    label: "last".into(),
14866                    ..Default::default()
14867                },
14868            ])))
14869        });
14870    cx.set_state("variableˇ");
14871    cx.simulate_keystroke(".");
14872    cx.executor().run_until_parked();
14873
14874    cx.update_editor(|editor, _, _| {
14875        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14876        {
14877            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14878        } else {
14879            panic!("expected completion menu to be open");
14880        }
14881    });
14882
14883    cx.update_editor(|editor, window, cx| {
14884        editor.move_page_down(&MovePageDown::default(), window, cx);
14885        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14886        {
14887            assert!(
14888                menu.selected_item == 1,
14889                "expected PageDown to select the last item from the context menu"
14890            );
14891        } else {
14892            panic!("expected completion menu to stay open after PageDown");
14893        }
14894    });
14895
14896    cx.update_editor(|editor, window, cx| {
14897        editor.move_page_up(&MovePageUp::default(), window, cx);
14898        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14899        {
14900            assert!(
14901                menu.selected_item == 0,
14902                "expected PageUp to select the first item from the context menu"
14903            );
14904        } else {
14905            panic!("expected completion menu to stay open after PageUp");
14906        }
14907    });
14908}
14909
14910#[gpui::test]
14911async fn test_as_is_completions(cx: &mut TestAppContext) {
14912    init_test(cx, |_| {});
14913    let mut cx = EditorLspTestContext::new_rust(
14914        lsp::ServerCapabilities {
14915            completion_provider: Some(lsp::CompletionOptions {
14916                ..Default::default()
14917            }),
14918            ..Default::default()
14919        },
14920        cx,
14921    )
14922    .await;
14923    cx.lsp
14924        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14925            Ok(Some(lsp::CompletionResponse::Array(vec![
14926                lsp::CompletionItem {
14927                    label: "unsafe".into(),
14928                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14929                        range: lsp::Range {
14930                            start: lsp::Position {
14931                                line: 1,
14932                                character: 2,
14933                            },
14934                            end: lsp::Position {
14935                                line: 1,
14936                                character: 3,
14937                            },
14938                        },
14939                        new_text: "unsafe".to_string(),
14940                    })),
14941                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
14942                    ..Default::default()
14943                },
14944            ])))
14945        });
14946    cx.set_state("fn a() {}\n");
14947    cx.executor().run_until_parked();
14948    cx.update_editor(|editor, window, cx| {
14949        editor.show_completions(
14950            &ShowCompletions {
14951                trigger: Some("\n".into()),
14952            },
14953            window,
14954            cx,
14955        );
14956    });
14957    cx.executor().run_until_parked();
14958
14959    cx.update_editor(|editor, window, cx| {
14960        editor.confirm_completion(&Default::default(), window, cx)
14961    });
14962    cx.executor().run_until_parked();
14963    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
14964}
14965
14966#[gpui::test]
14967async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
14968    init_test(cx, |_| {});
14969    let language =
14970        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
14971    let mut cx = EditorLspTestContext::new(
14972        language,
14973        lsp::ServerCapabilities {
14974            completion_provider: Some(lsp::CompletionOptions {
14975                ..lsp::CompletionOptions::default()
14976            }),
14977            ..lsp::ServerCapabilities::default()
14978        },
14979        cx,
14980    )
14981    .await;
14982
14983    cx.set_state(
14984        "#ifndef BAR_H
14985#define BAR_H
14986
14987#include <stdbool.h>
14988
14989int fn_branch(bool do_branch1, bool do_branch2);
14990
14991#endif // BAR_H
14992ˇ",
14993    );
14994    cx.executor().run_until_parked();
14995    cx.update_editor(|editor, window, cx| {
14996        editor.handle_input("#", window, cx);
14997    });
14998    cx.executor().run_until_parked();
14999    cx.update_editor(|editor, window, cx| {
15000        editor.handle_input("i", window, cx);
15001    });
15002    cx.executor().run_until_parked();
15003    cx.update_editor(|editor, window, cx| {
15004        editor.handle_input("n", window, cx);
15005    });
15006    cx.executor().run_until_parked();
15007    cx.assert_editor_state(
15008        "#ifndef BAR_H
15009#define BAR_H
15010
15011#include <stdbool.h>
15012
15013int fn_branch(bool do_branch1, bool do_branch2);
15014
15015#endif // BAR_H
15016#inˇ",
15017    );
15018
15019    cx.lsp
15020        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15021            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15022                is_incomplete: false,
15023                item_defaults: None,
15024                items: vec![lsp::CompletionItem {
15025                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15026                    label_details: Some(lsp::CompletionItemLabelDetails {
15027                        detail: Some("header".to_string()),
15028                        description: None,
15029                    }),
15030                    label: " include".to_string(),
15031                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15032                        range: lsp::Range {
15033                            start: lsp::Position {
15034                                line: 8,
15035                                character: 1,
15036                            },
15037                            end: lsp::Position {
15038                                line: 8,
15039                                character: 1,
15040                            },
15041                        },
15042                        new_text: "include \"$0\"".to_string(),
15043                    })),
15044                    sort_text: Some("40b67681include".to_string()),
15045                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15046                    filter_text: Some("include".to_string()),
15047                    insert_text: Some("include \"$0\"".to_string()),
15048                    ..lsp::CompletionItem::default()
15049                }],
15050            })))
15051        });
15052    cx.update_editor(|editor, window, cx| {
15053        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15054    });
15055    cx.executor().run_until_parked();
15056    cx.update_editor(|editor, window, cx| {
15057        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15058    });
15059    cx.executor().run_until_parked();
15060    cx.assert_editor_state(
15061        "#ifndef BAR_H
15062#define BAR_H
15063
15064#include <stdbool.h>
15065
15066int fn_branch(bool do_branch1, bool do_branch2);
15067
15068#endif // BAR_H
15069#include \"ˇ\"",
15070    );
15071
15072    cx.lsp
15073        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15074            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15075                is_incomplete: true,
15076                item_defaults: None,
15077                items: vec![lsp::CompletionItem {
15078                    kind: Some(lsp::CompletionItemKind::FILE),
15079                    label: "AGL/".to_string(),
15080                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15081                        range: lsp::Range {
15082                            start: lsp::Position {
15083                                line: 8,
15084                                character: 10,
15085                            },
15086                            end: lsp::Position {
15087                                line: 8,
15088                                character: 11,
15089                            },
15090                        },
15091                        new_text: "AGL/".to_string(),
15092                    })),
15093                    sort_text: Some("40b67681AGL/".to_string()),
15094                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15095                    filter_text: Some("AGL/".to_string()),
15096                    insert_text: Some("AGL/".to_string()),
15097                    ..lsp::CompletionItem::default()
15098                }],
15099            })))
15100        });
15101    cx.update_editor(|editor, window, cx| {
15102        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15103    });
15104    cx.executor().run_until_parked();
15105    cx.update_editor(|editor, window, cx| {
15106        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15107    });
15108    cx.executor().run_until_parked();
15109    cx.assert_editor_state(
15110        r##"#ifndef BAR_H
15111#define BAR_H
15112
15113#include <stdbool.h>
15114
15115int fn_branch(bool do_branch1, bool do_branch2);
15116
15117#endif // BAR_H
15118#include "AGL/ˇ"##,
15119    );
15120
15121    cx.update_editor(|editor, window, cx| {
15122        editor.handle_input("\"", window, cx);
15123    });
15124    cx.executor().run_until_parked();
15125    cx.assert_editor_state(
15126        r##"#ifndef BAR_H
15127#define BAR_H
15128
15129#include <stdbool.h>
15130
15131int fn_branch(bool do_branch1, bool do_branch2);
15132
15133#endif // BAR_H
15134#include "AGL/"ˇ"##,
15135    );
15136}
15137
15138#[gpui::test]
15139async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15140    init_test(cx, |_| {});
15141
15142    let mut cx = EditorLspTestContext::new_rust(
15143        lsp::ServerCapabilities {
15144            completion_provider: Some(lsp::CompletionOptions {
15145                trigger_characters: Some(vec![".".to_string()]),
15146                resolve_provider: Some(true),
15147                ..Default::default()
15148            }),
15149            ..Default::default()
15150        },
15151        cx,
15152    )
15153    .await;
15154
15155    cx.set_state("fn main() { let a = 2ˇ; }");
15156    cx.simulate_keystroke(".");
15157    let completion_item = lsp::CompletionItem {
15158        label: "Some".into(),
15159        kind: Some(lsp::CompletionItemKind::SNIPPET),
15160        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15161        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15162            kind: lsp::MarkupKind::Markdown,
15163            value: "```rust\nSome(2)\n```".to_string(),
15164        })),
15165        deprecated: Some(false),
15166        sort_text: Some("Some".to_string()),
15167        filter_text: Some("Some".to_string()),
15168        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15169        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15170            range: lsp::Range {
15171                start: lsp::Position {
15172                    line: 0,
15173                    character: 22,
15174                },
15175                end: lsp::Position {
15176                    line: 0,
15177                    character: 22,
15178                },
15179            },
15180            new_text: "Some(2)".to_string(),
15181        })),
15182        additional_text_edits: Some(vec![lsp::TextEdit {
15183            range: lsp::Range {
15184                start: lsp::Position {
15185                    line: 0,
15186                    character: 20,
15187                },
15188                end: lsp::Position {
15189                    line: 0,
15190                    character: 22,
15191                },
15192            },
15193            new_text: "".to_string(),
15194        }]),
15195        ..Default::default()
15196    };
15197
15198    let closure_completion_item = completion_item.clone();
15199    let counter = Arc::new(AtomicUsize::new(0));
15200    let counter_clone = counter.clone();
15201    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15202        let task_completion_item = closure_completion_item.clone();
15203        counter_clone.fetch_add(1, atomic::Ordering::Release);
15204        async move {
15205            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15206                is_incomplete: true,
15207                item_defaults: None,
15208                items: vec![task_completion_item],
15209            })))
15210        }
15211    });
15212
15213    cx.condition(|editor, _| editor.context_menu_visible())
15214        .await;
15215    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15216    assert!(request.next().await.is_some());
15217    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15218
15219    cx.simulate_keystrokes("S o m");
15220    cx.condition(|editor, _| editor.context_menu_visible())
15221        .await;
15222    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15223    assert!(request.next().await.is_some());
15224    assert!(request.next().await.is_some());
15225    assert!(request.next().await.is_some());
15226    request.close();
15227    assert!(request.next().await.is_none());
15228    assert_eq!(
15229        counter.load(atomic::Ordering::Acquire),
15230        4,
15231        "With the completions menu open, only one LSP request should happen per input"
15232    );
15233}
15234
15235#[gpui::test]
15236async fn test_toggle_comment(cx: &mut TestAppContext) {
15237    init_test(cx, |_| {});
15238    let mut cx = EditorTestContext::new(cx).await;
15239    let language = Arc::new(Language::new(
15240        LanguageConfig {
15241            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15242            ..Default::default()
15243        },
15244        Some(tree_sitter_rust::LANGUAGE.into()),
15245    ));
15246    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15247
15248    // If multiple selections intersect a line, the line is only toggled once.
15249    cx.set_state(indoc! {"
15250        fn a() {
15251            «//b();
15252            ˇ»// «c();
15253            //ˇ»  d();
15254        }
15255    "});
15256
15257    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15258
15259    cx.assert_editor_state(indoc! {"
15260        fn a() {
15261            «b();
15262            c();
15263            ˇ» d();
15264        }
15265    "});
15266
15267    // The comment prefix is inserted at the same column for every line in a
15268    // selection.
15269    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15270
15271    cx.assert_editor_state(indoc! {"
15272        fn a() {
15273            // «b();
15274            // c();
15275            ˇ»//  d();
15276        }
15277    "});
15278
15279    // If a selection ends at the beginning of a line, that line is not toggled.
15280    cx.set_selections_state(indoc! {"
15281        fn a() {
15282            // b();
15283            «// c();
15284        ˇ»    //  d();
15285        }
15286    "});
15287
15288    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15289
15290    cx.assert_editor_state(indoc! {"
15291        fn a() {
15292            // b();
15293            «c();
15294        ˇ»    //  d();
15295        }
15296    "});
15297
15298    // If a selection span a single line and is empty, the line is toggled.
15299    cx.set_state(indoc! {"
15300        fn a() {
15301            a();
15302            b();
15303        ˇ
15304        }
15305    "});
15306
15307    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15308
15309    cx.assert_editor_state(indoc! {"
15310        fn a() {
15311            a();
15312            b();
15313        //•ˇ
15314        }
15315    "});
15316
15317    // If a selection span multiple lines, empty lines are not toggled.
15318    cx.set_state(indoc! {"
15319        fn a() {
15320            «a();
15321
15322            c();ˇ»
15323        }
15324    "});
15325
15326    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15327
15328    cx.assert_editor_state(indoc! {"
15329        fn a() {
15330            // «a();
15331
15332            // c();ˇ»
15333        }
15334    "});
15335
15336    // If a selection includes multiple comment prefixes, all lines are uncommented.
15337    cx.set_state(indoc! {"
15338        fn a() {
15339            «// a();
15340            /// b();
15341            //! c();ˇ»
15342        }
15343    "});
15344
15345    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15346
15347    cx.assert_editor_state(indoc! {"
15348        fn a() {
15349            «a();
15350            b();
15351            c();ˇ»
15352        }
15353    "});
15354}
15355
15356#[gpui::test]
15357async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15358    init_test(cx, |_| {});
15359    let mut cx = EditorTestContext::new(cx).await;
15360    let language = Arc::new(Language::new(
15361        LanguageConfig {
15362            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15363            ..Default::default()
15364        },
15365        Some(tree_sitter_rust::LANGUAGE.into()),
15366    ));
15367    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15368
15369    let toggle_comments = &ToggleComments {
15370        advance_downwards: false,
15371        ignore_indent: true,
15372    };
15373
15374    // If multiple selections intersect a line, the line is only toggled once.
15375    cx.set_state(indoc! {"
15376        fn a() {
15377        //    «b();
15378        //    c();
15379        //    ˇ» d();
15380        }
15381    "});
15382
15383    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15384
15385    cx.assert_editor_state(indoc! {"
15386        fn a() {
15387            «b();
15388            c();
15389            ˇ» d();
15390        }
15391    "});
15392
15393    // The comment prefix is inserted at the beginning of each line
15394    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15395
15396    cx.assert_editor_state(indoc! {"
15397        fn a() {
15398        //    «b();
15399        //    c();
15400        //    ˇ» d();
15401        }
15402    "});
15403
15404    // If a selection ends at the beginning of a line, that line is not toggled.
15405    cx.set_selections_state(indoc! {"
15406        fn a() {
15407        //    b();
15408        //    «c();
15409        ˇ»//     d();
15410        }
15411    "});
15412
15413    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15414
15415    cx.assert_editor_state(indoc! {"
15416        fn a() {
15417        //    b();
15418            «c();
15419        ˇ»//     d();
15420        }
15421    "});
15422
15423    // If a selection span a single line and is empty, the line is toggled.
15424    cx.set_state(indoc! {"
15425        fn a() {
15426            a();
15427            b();
15428        ˇ
15429        }
15430    "});
15431
15432    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15433
15434    cx.assert_editor_state(indoc! {"
15435        fn a() {
15436            a();
15437            b();
15438        //ˇ
15439        }
15440    "});
15441
15442    // If a selection span multiple lines, empty lines are not toggled.
15443    cx.set_state(indoc! {"
15444        fn a() {
15445            «a();
15446
15447            c();ˇ»
15448        }
15449    "});
15450
15451    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15452
15453    cx.assert_editor_state(indoc! {"
15454        fn a() {
15455        //    «a();
15456
15457        //    c();ˇ»
15458        }
15459    "});
15460
15461    // If a selection includes multiple comment prefixes, all lines are uncommented.
15462    cx.set_state(indoc! {"
15463        fn a() {
15464        //    «a();
15465        ///    b();
15466        //!    c();ˇ»
15467        }
15468    "});
15469
15470    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15471
15472    cx.assert_editor_state(indoc! {"
15473        fn a() {
15474            «a();
15475            b();
15476            c();ˇ»
15477        }
15478    "});
15479}
15480
15481#[gpui::test]
15482async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15483    init_test(cx, |_| {});
15484
15485    let language = Arc::new(Language::new(
15486        LanguageConfig {
15487            line_comments: vec!["// ".into()],
15488            ..Default::default()
15489        },
15490        Some(tree_sitter_rust::LANGUAGE.into()),
15491    ));
15492
15493    let mut cx = EditorTestContext::new(cx).await;
15494
15495    cx.language_registry().add(language.clone());
15496    cx.update_buffer(|buffer, cx| {
15497        buffer.set_language(Some(language), cx);
15498    });
15499
15500    let toggle_comments = &ToggleComments {
15501        advance_downwards: true,
15502        ignore_indent: false,
15503    };
15504
15505    // Single cursor on one line -> advance
15506    // Cursor moves horizontally 3 characters as well on non-blank line
15507    cx.set_state(indoc!(
15508        "fn a() {
15509             ˇdog();
15510             cat();
15511        }"
15512    ));
15513    cx.update_editor(|editor, window, cx| {
15514        editor.toggle_comments(toggle_comments, window, cx);
15515    });
15516    cx.assert_editor_state(indoc!(
15517        "fn a() {
15518             // dog();
15519             catˇ();
15520        }"
15521    ));
15522
15523    // Single selection on one line -> don't advance
15524    cx.set_state(indoc!(
15525        "fn a() {
15526             «dog()ˇ»;
15527             cat();
15528        }"
15529    ));
15530    cx.update_editor(|editor, window, cx| {
15531        editor.toggle_comments(toggle_comments, window, cx);
15532    });
15533    cx.assert_editor_state(indoc!(
15534        "fn a() {
15535             // «dog()ˇ»;
15536             cat();
15537        }"
15538    ));
15539
15540    // Multiple cursors on one line -> advance
15541    cx.set_state(indoc!(
15542        "fn a() {
15543             ˇdˇog();
15544             cat();
15545        }"
15546    ));
15547    cx.update_editor(|editor, window, cx| {
15548        editor.toggle_comments(toggle_comments, window, cx);
15549    });
15550    cx.assert_editor_state(indoc!(
15551        "fn a() {
15552             // dog();
15553             catˇ(ˇ);
15554        }"
15555    ));
15556
15557    // Multiple cursors on one line, with selection -> don't advance
15558    cx.set_state(indoc!(
15559        "fn a() {
15560             ˇdˇog«()ˇ»;
15561             cat();
15562        }"
15563    ));
15564    cx.update_editor(|editor, window, cx| {
15565        editor.toggle_comments(toggle_comments, window, cx);
15566    });
15567    cx.assert_editor_state(indoc!(
15568        "fn a() {
15569             // ˇdˇog«()ˇ»;
15570             cat();
15571        }"
15572    ));
15573
15574    // Single cursor on one line -> advance
15575    // Cursor moves to column 0 on blank line
15576    cx.set_state(indoc!(
15577        "fn a() {
15578             ˇdog();
15579
15580             cat();
15581        }"
15582    ));
15583    cx.update_editor(|editor, window, cx| {
15584        editor.toggle_comments(toggle_comments, window, cx);
15585    });
15586    cx.assert_editor_state(indoc!(
15587        "fn a() {
15588             // dog();
15589        ˇ
15590             cat();
15591        }"
15592    ));
15593
15594    // Single cursor on one line -> advance
15595    // Cursor starts and ends at column 0
15596    cx.set_state(indoc!(
15597        "fn a() {
15598         ˇ    dog();
15599             cat();
15600        }"
15601    ));
15602    cx.update_editor(|editor, window, cx| {
15603        editor.toggle_comments(toggle_comments, window, cx);
15604    });
15605    cx.assert_editor_state(indoc!(
15606        "fn a() {
15607             // dog();
15608         ˇ    cat();
15609        }"
15610    ));
15611}
15612
15613#[gpui::test]
15614async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15615    init_test(cx, |_| {});
15616
15617    let mut cx = EditorTestContext::new(cx).await;
15618
15619    let html_language = Arc::new(
15620        Language::new(
15621            LanguageConfig {
15622                name: "HTML".into(),
15623                block_comment: Some(BlockCommentConfig {
15624                    start: "<!-- ".into(),
15625                    prefix: "".into(),
15626                    end: " -->".into(),
15627                    tab_size: 0,
15628                }),
15629                ..Default::default()
15630            },
15631            Some(tree_sitter_html::LANGUAGE.into()),
15632        )
15633        .with_injection_query(
15634            r#"
15635            (script_element
15636                (raw_text) @injection.content
15637                (#set! injection.language "javascript"))
15638            "#,
15639        )
15640        .unwrap(),
15641    );
15642
15643    let javascript_language = Arc::new(Language::new(
15644        LanguageConfig {
15645            name: "JavaScript".into(),
15646            line_comments: vec!["// ".into()],
15647            ..Default::default()
15648        },
15649        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15650    ));
15651
15652    cx.language_registry().add(html_language.clone());
15653    cx.language_registry().add(javascript_language);
15654    cx.update_buffer(|buffer, cx| {
15655        buffer.set_language(Some(html_language), cx);
15656    });
15657
15658    // Toggle comments for empty selections
15659    cx.set_state(
15660        &r#"
15661            <p>A</p>ˇ
15662            <p>B</p>ˇ
15663            <p>C</p>ˇ
15664        "#
15665        .unindent(),
15666    );
15667    cx.update_editor(|editor, window, cx| {
15668        editor.toggle_comments(&ToggleComments::default(), window, cx)
15669    });
15670    cx.assert_editor_state(
15671        &r#"
15672            <!-- <p>A</p>ˇ -->
15673            <!-- <p>B</p>ˇ -->
15674            <!-- <p>C</p>ˇ -->
15675        "#
15676        .unindent(),
15677    );
15678    cx.update_editor(|editor, window, cx| {
15679        editor.toggle_comments(&ToggleComments::default(), window, cx)
15680    });
15681    cx.assert_editor_state(
15682        &r#"
15683            <p>A</p>ˇ
15684            <p>B</p>ˇ
15685            <p>C</p>ˇ
15686        "#
15687        .unindent(),
15688    );
15689
15690    // Toggle comments for mixture of empty and non-empty selections, where
15691    // multiple selections occupy a given line.
15692    cx.set_state(
15693        &r#"
15694            <p>A«</p>
15695            <p>ˇ»B</p>ˇ
15696            <p>C«</p>
15697            <p>ˇ»D</p>ˇ
15698        "#
15699        .unindent(),
15700    );
15701
15702    cx.update_editor(|editor, window, cx| {
15703        editor.toggle_comments(&ToggleComments::default(), window, cx)
15704    });
15705    cx.assert_editor_state(
15706        &r#"
15707            <!-- <p>A«</p>
15708            <p>ˇ»B</p>ˇ -->
15709            <!-- <p>C«</p>
15710            <p>ˇ»D</p>ˇ -->
15711        "#
15712        .unindent(),
15713    );
15714    cx.update_editor(|editor, window, cx| {
15715        editor.toggle_comments(&ToggleComments::default(), window, cx)
15716    });
15717    cx.assert_editor_state(
15718        &r#"
15719            <p>A«</p>
15720            <p>ˇ»B</p>ˇ
15721            <p>C«</p>
15722            <p>ˇ»D</p>ˇ
15723        "#
15724        .unindent(),
15725    );
15726
15727    // Toggle comments when different languages are active for different
15728    // selections.
15729    cx.set_state(
15730        &r#"
15731            ˇ<script>
15732                ˇvar x = new Y();
15733            ˇ</script>
15734        "#
15735        .unindent(),
15736    );
15737    cx.executor().run_until_parked();
15738    cx.update_editor(|editor, window, cx| {
15739        editor.toggle_comments(&ToggleComments::default(), window, cx)
15740    });
15741    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15742    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15743    cx.assert_editor_state(
15744        &r#"
15745            <!-- ˇ<script> -->
15746                // ˇvar x = new Y();
15747            <!-- ˇ</script> -->
15748        "#
15749        .unindent(),
15750    );
15751}
15752
15753#[gpui::test]
15754fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15755    init_test(cx, |_| {});
15756
15757    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15758    let multibuffer = cx.new(|cx| {
15759        let mut multibuffer = MultiBuffer::new(ReadWrite);
15760        multibuffer.push_excerpts(
15761            buffer.clone(),
15762            [
15763                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15764                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15765            ],
15766            cx,
15767        );
15768        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15769        multibuffer
15770    });
15771
15772    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15773    editor.update_in(cx, |editor, window, cx| {
15774        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15775        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15776            s.select_ranges([
15777                Point::new(0, 0)..Point::new(0, 0),
15778                Point::new(1, 0)..Point::new(1, 0),
15779            ])
15780        });
15781
15782        editor.handle_input("X", window, cx);
15783        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15784        assert_eq!(
15785            editor.selections.ranges(cx),
15786            [
15787                Point::new(0, 1)..Point::new(0, 1),
15788                Point::new(1, 1)..Point::new(1, 1),
15789            ]
15790        );
15791
15792        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15793        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15794            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15795        });
15796        editor.backspace(&Default::default(), window, cx);
15797        assert_eq!(editor.text(cx), "Xa\nbbb");
15798        assert_eq!(
15799            editor.selections.ranges(cx),
15800            [Point::new(1, 0)..Point::new(1, 0)]
15801        );
15802
15803        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15804            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15805        });
15806        editor.backspace(&Default::default(), window, cx);
15807        assert_eq!(editor.text(cx), "X\nbb");
15808        assert_eq!(
15809            editor.selections.ranges(cx),
15810            [Point::new(0, 1)..Point::new(0, 1)]
15811        );
15812    });
15813}
15814
15815#[gpui::test]
15816fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15817    init_test(cx, |_| {});
15818
15819    let markers = vec![('[', ']').into(), ('(', ')').into()];
15820    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15821        indoc! {"
15822            [aaaa
15823            (bbbb]
15824            cccc)",
15825        },
15826        markers.clone(),
15827    );
15828    let excerpt_ranges = markers.into_iter().map(|marker| {
15829        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15830        ExcerptRange::new(context)
15831    });
15832    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15833    let multibuffer = cx.new(|cx| {
15834        let mut multibuffer = MultiBuffer::new(ReadWrite);
15835        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15836        multibuffer
15837    });
15838
15839    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15840    editor.update_in(cx, |editor, window, cx| {
15841        let (expected_text, selection_ranges) = marked_text_ranges(
15842            indoc! {"
15843                aaaa
15844                bˇbbb
15845                bˇbbˇb
15846                cccc"
15847            },
15848            true,
15849        );
15850        assert_eq!(editor.text(cx), expected_text);
15851        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15852            s.select_ranges(selection_ranges)
15853        });
15854
15855        editor.handle_input("X", window, cx);
15856
15857        let (expected_text, expected_selections) = marked_text_ranges(
15858            indoc! {"
15859                aaaa
15860                bXˇbbXb
15861                bXˇbbXˇb
15862                cccc"
15863            },
15864            false,
15865        );
15866        assert_eq!(editor.text(cx), expected_text);
15867        assert_eq!(editor.selections.ranges(cx), expected_selections);
15868
15869        editor.newline(&Newline, window, cx);
15870        let (expected_text, expected_selections) = marked_text_ranges(
15871            indoc! {"
15872                aaaa
15873                bX
15874                ˇbbX
15875                b
15876                bX
15877                ˇbbX
15878                ˇb
15879                cccc"
15880            },
15881            false,
15882        );
15883        assert_eq!(editor.text(cx), expected_text);
15884        assert_eq!(editor.selections.ranges(cx), expected_selections);
15885    });
15886}
15887
15888#[gpui::test]
15889fn test_refresh_selections(cx: &mut TestAppContext) {
15890    init_test(cx, |_| {});
15891
15892    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15893    let mut excerpt1_id = None;
15894    let multibuffer = cx.new(|cx| {
15895        let mut multibuffer = MultiBuffer::new(ReadWrite);
15896        excerpt1_id = multibuffer
15897            .push_excerpts(
15898                buffer.clone(),
15899                [
15900                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15901                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15902                ],
15903                cx,
15904            )
15905            .into_iter()
15906            .next();
15907        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15908        multibuffer
15909    });
15910
15911    let editor = cx.add_window(|window, cx| {
15912        let mut editor = build_editor(multibuffer.clone(), window, cx);
15913        let snapshot = editor.snapshot(window, cx);
15914        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15915            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15916        });
15917        editor.begin_selection(
15918            Point::new(2, 1).to_display_point(&snapshot),
15919            true,
15920            1,
15921            window,
15922            cx,
15923        );
15924        assert_eq!(
15925            editor.selections.ranges(cx),
15926            [
15927                Point::new(1, 3)..Point::new(1, 3),
15928                Point::new(2, 1)..Point::new(2, 1),
15929            ]
15930        );
15931        editor
15932    });
15933
15934    // Refreshing selections is a no-op when excerpts haven't changed.
15935    _ = editor.update(cx, |editor, window, cx| {
15936        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15937        assert_eq!(
15938            editor.selections.ranges(cx),
15939            [
15940                Point::new(1, 3)..Point::new(1, 3),
15941                Point::new(2, 1)..Point::new(2, 1),
15942            ]
15943        );
15944    });
15945
15946    multibuffer.update(cx, |multibuffer, cx| {
15947        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15948    });
15949    _ = editor.update(cx, |editor, window, cx| {
15950        // Removing an excerpt causes the first selection to become degenerate.
15951        assert_eq!(
15952            editor.selections.ranges(cx),
15953            [
15954                Point::new(0, 0)..Point::new(0, 0),
15955                Point::new(0, 1)..Point::new(0, 1)
15956            ]
15957        );
15958
15959        // Refreshing selections will relocate the first selection to the original buffer
15960        // location.
15961        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15962        assert_eq!(
15963            editor.selections.ranges(cx),
15964            [
15965                Point::new(0, 1)..Point::new(0, 1),
15966                Point::new(0, 3)..Point::new(0, 3)
15967            ]
15968        );
15969        assert!(editor.selections.pending_anchor().is_some());
15970    });
15971}
15972
15973#[gpui::test]
15974fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
15975    init_test(cx, |_| {});
15976
15977    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15978    let mut excerpt1_id = None;
15979    let multibuffer = cx.new(|cx| {
15980        let mut multibuffer = MultiBuffer::new(ReadWrite);
15981        excerpt1_id = multibuffer
15982            .push_excerpts(
15983                buffer.clone(),
15984                [
15985                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15986                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15987                ],
15988                cx,
15989            )
15990            .into_iter()
15991            .next();
15992        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15993        multibuffer
15994    });
15995
15996    let editor = cx.add_window(|window, cx| {
15997        let mut editor = build_editor(multibuffer.clone(), window, cx);
15998        let snapshot = editor.snapshot(window, cx);
15999        editor.begin_selection(
16000            Point::new(1, 3).to_display_point(&snapshot),
16001            false,
16002            1,
16003            window,
16004            cx,
16005        );
16006        assert_eq!(
16007            editor.selections.ranges(cx),
16008            [Point::new(1, 3)..Point::new(1, 3)]
16009        );
16010        editor
16011    });
16012
16013    multibuffer.update(cx, |multibuffer, cx| {
16014        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16015    });
16016    _ = editor.update(cx, |editor, window, cx| {
16017        assert_eq!(
16018            editor.selections.ranges(cx),
16019            [Point::new(0, 0)..Point::new(0, 0)]
16020        );
16021
16022        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16023        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16024        assert_eq!(
16025            editor.selections.ranges(cx),
16026            [Point::new(0, 3)..Point::new(0, 3)]
16027        );
16028        assert!(editor.selections.pending_anchor().is_some());
16029    });
16030}
16031
16032#[gpui::test]
16033async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16034    init_test(cx, |_| {});
16035
16036    let language = Arc::new(
16037        Language::new(
16038            LanguageConfig {
16039                brackets: BracketPairConfig {
16040                    pairs: vec![
16041                        BracketPair {
16042                            start: "{".to_string(),
16043                            end: "}".to_string(),
16044                            close: true,
16045                            surround: true,
16046                            newline: true,
16047                        },
16048                        BracketPair {
16049                            start: "/* ".to_string(),
16050                            end: " */".to_string(),
16051                            close: true,
16052                            surround: true,
16053                            newline: true,
16054                        },
16055                    ],
16056                    ..Default::default()
16057                },
16058                ..Default::default()
16059            },
16060            Some(tree_sitter_rust::LANGUAGE.into()),
16061        )
16062        .with_indents_query("")
16063        .unwrap(),
16064    );
16065
16066    let text = concat!(
16067        "{   }\n",     //
16068        "  x\n",       //
16069        "  /*   */\n", //
16070        "x\n",         //
16071        "{{} }\n",     //
16072    );
16073
16074    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16075    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16076    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16077    editor
16078        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16079        .await;
16080
16081    editor.update_in(cx, |editor, window, cx| {
16082        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16083            s.select_display_ranges([
16084                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16085                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16086                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16087            ])
16088        });
16089        editor.newline(&Newline, window, cx);
16090
16091        assert_eq!(
16092            editor.buffer().read(cx).read(cx).text(),
16093            concat!(
16094                "{ \n",    // Suppress rustfmt
16095                "\n",      //
16096                "}\n",     //
16097                "  x\n",   //
16098                "  /* \n", //
16099                "  \n",    //
16100                "  */\n",  //
16101                "x\n",     //
16102                "{{} \n",  //
16103                "}\n",     //
16104            )
16105        );
16106    });
16107}
16108
16109#[gpui::test]
16110fn test_highlighted_ranges(cx: &mut TestAppContext) {
16111    init_test(cx, |_| {});
16112
16113    let editor = cx.add_window(|window, cx| {
16114        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16115        build_editor(buffer, window, cx)
16116    });
16117
16118    _ = editor.update(cx, |editor, window, cx| {
16119        struct Type1;
16120        struct Type2;
16121
16122        let buffer = editor.buffer.read(cx).snapshot(cx);
16123
16124        let anchor_range =
16125            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16126
16127        editor.highlight_background::<Type1>(
16128            &[
16129                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16130                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16131                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16132                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16133            ],
16134            |_| Hsla::red(),
16135            cx,
16136        );
16137        editor.highlight_background::<Type2>(
16138            &[
16139                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16140                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16141                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16142                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16143            ],
16144            |_| Hsla::green(),
16145            cx,
16146        );
16147
16148        let snapshot = editor.snapshot(window, cx);
16149        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16150            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16151            &snapshot,
16152            cx.theme(),
16153        );
16154        assert_eq!(
16155            highlighted_ranges,
16156            &[
16157                (
16158                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16159                    Hsla::green(),
16160                ),
16161                (
16162                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16163                    Hsla::red(),
16164                ),
16165                (
16166                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16167                    Hsla::green(),
16168                ),
16169                (
16170                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16171                    Hsla::red(),
16172                ),
16173            ]
16174        );
16175        assert_eq!(
16176            editor.sorted_background_highlights_in_range(
16177                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16178                &snapshot,
16179                cx.theme(),
16180            ),
16181            &[(
16182                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16183                Hsla::red(),
16184            )]
16185        );
16186    });
16187}
16188
16189#[gpui::test]
16190async fn test_following(cx: &mut TestAppContext) {
16191    init_test(cx, |_| {});
16192
16193    let fs = FakeFs::new(cx.executor());
16194    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16195
16196    let buffer = project.update(cx, |project, cx| {
16197        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16198        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16199    });
16200    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16201    let follower = cx.update(|cx| {
16202        cx.open_window(
16203            WindowOptions {
16204                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16205                    gpui::Point::new(px(0.), px(0.)),
16206                    gpui::Point::new(px(10.), px(80.)),
16207                ))),
16208                ..Default::default()
16209            },
16210            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16211        )
16212        .unwrap()
16213    });
16214
16215    let is_still_following = Rc::new(RefCell::new(true));
16216    let follower_edit_event_count = Rc::new(RefCell::new(0));
16217    let pending_update = Rc::new(RefCell::new(None));
16218    let leader_entity = leader.root(cx).unwrap();
16219    let follower_entity = follower.root(cx).unwrap();
16220    _ = follower.update(cx, {
16221        let update = pending_update.clone();
16222        let is_still_following = is_still_following.clone();
16223        let follower_edit_event_count = follower_edit_event_count.clone();
16224        |_, window, cx| {
16225            cx.subscribe_in(
16226                &leader_entity,
16227                window,
16228                move |_, leader, event, window, cx| {
16229                    leader.read(cx).add_event_to_update_proto(
16230                        event,
16231                        &mut update.borrow_mut(),
16232                        window,
16233                        cx,
16234                    );
16235                },
16236            )
16237            .detach();
16238
16239            cx.subscribe_in(
16240                &follower_entity,
16241                window,
16242                move |_, _, event: &EditorEvent, _window, _cx| {
16243                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16244                        *is_still_following.borrow_mut() = false;
16245                    }
16246
16247                    if let EditorEvent::BufferEdited = event {
16248                        *follower_edit_event_count.borrow_mut() += 1;
16249                    }
16250                },
16251            )
16252            .detach();
16253        }
16254    });
16255
16256    // Update the selections only
16257    _ = leader.update(cx, |leader, window, cx| {
16258        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16259            s.select_ranges([1..1])
16260        });
16261    });
16262    follower
16263        .update(cx, |follower, window, cx| {
16264            follower.apply_update_proto(
16265                &project,
16266                pending_update.borrow_mut().take().unwrap(),
16267                window,
16268                cx,
16269            )
16270        })
16271        .unwrap()
16272        .await
16273        .unwrap();
16274    _ = follower.update(cx, |follower, _, cx| {
16275        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
16276    });
16277    assert!(*is_still_following.borrow());
16278    assert_eq!(*follower_edit_event_count.borrow(), 0);
16279
16280    // Update the scroll position only
16281    _ = leader.update(cx, |leader, window, cx| {
16282        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16283    });
16284    follower
16285        .update(cx, |follower, window, cx| {
16286            follower.apply_update_proto(
16287                &project,
16288                pending_update.borrow_mut().take().unwrap(),
16289                window,
16290                cx,
16291            )
16292        })
16293        .unwrap()
16294        .await
16295        .unwrap();
16296    assert_eq!(
16297        follower
16298            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16299            .unwrap(),
16300        gpui::Point::new(1.5, 3.5)
16301    );
16302    assert!(*is_still_following.borrow());
16303    assert_eq!(*follower_edit_event_count.borrow(), 0);
16304
16305    // Update the selections and scroll position. The follower's scroll position is updated
16306    // via autoscroll, not via the leader's exact scroll position.
16307    _ = leader.update(cx, |leader, window, cx| {
16308        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16309            s.select_ranges([0..0])
16310        });
16311        leader.request_autoscroll(Autoscroll::newest(), cx);
16312        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16313    });
16314    follower
16315        .update(cx, |follower, window, cx| {
16316            follower.apply_update_proto(
16317                &project,
16318                pending_update.borrow_mut().take().unwrap(),
16319                window,
16320                cx,
16321            )
16322        })
16323        .unwrap()
16324        .await
16325        .unwrap();
16326    _ = follower.update(cx, |follower, _, cx| {
16327        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16328        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16329    });
16330    assert!(*is_still_following.borrow());
16331
16332    // Creating a pending selection that precedes another selection
16333    _ = leader.update(cx, |leader, window, cx| {
16334        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16335            s.select_ranges([1..1])
16336        });
16337        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16338    });
16339    follower
16340        .update(cx, |follower, window, cx| {
16341            follower.apply_update_proto(
16342                &project,
16343                pending_update.borrow_mut().take().unwrap(),
16344                window,
16345                cx,
16346            )
16347        })
16348        .unwrap()
16349        .await
16350        .unwrap();
16351    _ = follower.update(cx, |follower, _, cx| {
16352        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16353    });
16354    assert!(*is_still_following.borrow());
16355
16356    // Extend the pending selection so that it surrounds another selection
16357    _ = leader.update(cx, |leader, window, cx| {
16358        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16359    });
16360    follower
16361        .update(cx, |follower, window, cx| {
16362            follower.apply_update_proto(
16363                &project,
16364                pending_update.borrow_mut().take().unwrap(),
16365                window,
16366                cx,
16367            )
16368        })
16369        .unwrap()
16370        .await
16371        .unwrap();
16372    _ = follower.update(cx, |follower, _, cx| {
16373        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16374    });
16375
16376    // Scrolling locally breaks the follow
16377    _ = follower.update(cx, |follower, window, cx| {
16378        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16379        follower.set_scroll_anchor(
16380            ScrollAnchor {
16381                anchor: top_anchor,
16382                offset: gpui::Point::new(0.0, 0.5),
16383            },
16384            window,
16385            cx,
16386        );
16387    });
16388    assert!(!(*is_still_following.borrow()));
16389}
16390
16391#[gpui::test]
16392async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16393    init_test(cx, |_| {});
16394
16395    let fs = FakeFs::new(cx.executor());
16396    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16397    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16398    let pane = workspace
16399        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16400        .unwrap();
16401
16402    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16403
16404    let leader = pane.update_in(cx, |_, window, cx| {
16405        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16406        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16407    });
16408
16409    // Start following the editor when it has no excerpts.
16410    let mut state_message =
16411        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16412    let workspace_entity = workspace.root(cx).unwrap();
16413    let follower_1 = cx
16414        .update_window(*workspace.deref(), |_, window, cx| {
16415            Editor::from_state_proto(
16416                workspace_entity,
16417                ViewId {
16418                    creator: CollaboratorId::PeerId(PeerId::default()),
16419                    id: 0,
16420                },
16421                &mut state_message,
16422                window,
16423                cx,
16424            )
16425        })
16426        .unwrap()
16427        .unwrap()
16428        .await
16429        .unwrap();
16430
16431    let update_message = Rc::new(RefCell::new(None));
16432    follower_1.update_in(cx, {
16433        let update = update_message.clone();
16434        |_, window, cx| {
16435            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16436                leader.read(cx).add_event_to_update_proto(
16437                    event,
16438                    &mut update.borrow_mut(),
16439                    window,
16440                    cx,
16441                );
16442            })
16443            .detach();
16444        }
16445    });
16446
16447    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16448        (
16449            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16450            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16451        )
16452    });
16453
16454    // Insert some excerpts.
16455    leader.update(cx, |leader, cx| {
16456        leader.buffer.update(cx, |multibuffer, cx| {
16457            multibuffer.set_excerpts_for_path(
16458                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
16459                buffer_1.clone(),
16460                vec![
16461                    Point::row_range(0..3),
16462                    Point::row_range(1..6),
16463                    Point::row_range(12..15),
16464                ],
16465                0,
16466                cx,
16467            );
16468            multibuffer.set_excerpts_for_path(
16469                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
16470                buffer_2.clone(),
16471                vec![Point::row_range(0..6), Point::row_range(8..12)],
16472                0,
16473                cx,
16474            );
16475        });
16476    });
16477
16478    // Apply the update of adding the excerpts.
16479    follower_1
16480        .update_in(cx, |follower, window, cx| {
16481            follower.apply_update_proto(
16482                &project,
16483                update_message.borrow().clone().unwrap(),
16484                window,
16485                cx,
16486            )
16487        })
16488        .await
16489        .unwrap();
16490    assert_eq!(
16491        follower_1.update(cx, |editor, cx| editor.text(cx)),
16492        leader.update(cx, |editor, cx| editor.text(cx))
16493    );
16494    update_message.borrow_mut().take();
16495
16496    // Start following separately after it already has excerpts.
16497    let mut state_message =
16498        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16499    let workspace_entity = workspace.root(cx).unwrap();
16500    let follower_2 = cx
16501        .update_window(*workspace.deref(), |_, window, cx| {
16502            Editor::from_state_proto(
16503                workspace_entity,
16504                ViewId {
16505                    creator: CollaboratorId::PeerId(PeerId::default()),
16506                    id: 0,
16507                },
16508                &mut state_message,
16509                window,
16510                cx,
16511            )
16512        })
16513        .unwrap()
16514        .unwrap()
16515        .await
16516        .unwrap();
16517    assert_eq!(
16518        follower_2.update(cx, |editor, cx| editor.text(cx)),
16519        leader.update(cx, |editor, cx| editor.text(cx))
16520    );
16521
16522    // Remove some excerpts.
16523    leader.update(cx, |leader, cx| {
16524        leader.buffer.update(cx, |multibuffer, cx| {
16525            let excerpt_ids = multibuffer.excerpt_ids();
16526            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16527            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16528        });
16529    });
16530
16531    // Apply the update of removing the excerpts.
16532    follower_1
16533        .update_in(cx, |follower, window, cx| {
16534            follower.apply_update_proto(
16535                &project,
16536                update_message.borrow().clone().unwrap(),
16537                window,
16538                cx,
16539            )
16540        })
16541        .await
16542        .unwrap();
16543    follower_2
16544        .update_in(cx, |follower, window, cx| {
16545            follower.apply_update_proto(
16546                &project,
16547                update_message.borrow().clone().unwrap(),
16548                window,
16549                cx,
16550            )
16551        })
16552        .await
16553        .unwrap();
16554    update_message.borrow_mut().take();
16555    assert_eq!(
16556        follower_1.update(cx, |editor, cx| editor.text(cx)),
16557        leader.update(cx, |editor, cx| editor.text(cx))
16558    );
16559}
16560
16561#[gpui::test]
16562async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16563    init_test(cx, |_| {});
16564
16565    let mut cx = EditorTestContext::new(cx).await;
16566    let lsp_store =
16567        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16568
16569    cx.set_state(indoc! {"
16570        ˇfn func(abc def: i32) -> u32 {
16571        }
16572    "});
16573
16574    cx.update(|_, cx| {
16575        lsp_store.update(cx, |lsp_store, cx| {
16576            lsp_store
16577                .update_diagnostics(
16578                    LanguageServerId(0),
16579                    lsp::PublishDiagnosticsParams {
16580                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16581                        version: None,
16582                        diagnostics: vec![
16583                            lsp::Diagnostic {
16584                                range: lsp::Range::new(
16585                                    lsp::Position::new(0, 11),
16586                                    lsp::Position::new(0, 12),
16587                                ),
16588                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16589                                ..Default::default()
16590                            },
16591                            lsp::Diagnostic {
16592                                range: lsp::Range::new(
16593                                    lsp::Position::new(0, 12),
16594                                    lsp::Position::new(0, 15),
16595                                ),
16596                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16597                                ..Default::default()
16598                            },
16599                            lsp::Diagnostic {
16600                                range: lsp::Range::new(
16601                                    lsp::Position::new(0, 25),
16602                                    lsp::Position::new(0, 28),
16603                                ),
16604                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16605                                ..Default::default()
16606                            },
16607                        ],
16608                    },
16609                    None,
16610                    DiagnosticSourceKind::Pushed,
16611                    &[],
16612                    cx,
16613                )
16614                .unwrap()
16615        });
16616    });
16617
16618    executor.run_until_parked();
16619
16620    cx.update_editor(|editor, window, cx| {
16621        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16622    });
16623
16624    cx.assert_editor_state(indoc! {"
16625        fn func(abc def: i32) -> ˇu32 {
16626        }
16627    "});
16628
16629    cx.update_editor(|editor, window, cx| {
16630        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16631    });
16632
16633    cx.assert_editor_state(indoc! {"
16634        fn func(abc ˇdef: i32) -> u32 {
16635        }
16636    "});
16637
16638    cx.update_editor(|editor, window, cx| {
16639        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16640    });
16641
16642    cx.assert_editor_state(indoc! {"
16643        fn func(abcˇ def: i32) -> u32 {
16644        }
16645    "});
16646
16647    cx.update_editor(|editor, window, cx| {
16648        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16649    });
16650
16651    cx.assert_editor_state(indoc! {"
16652        fn func(abc def: i32) -> ˇu32 {
16653        }
16654    "});
16655}
16656
16657#[gpui::test]
16658async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16659    init_test(cx, |_| {});
16660
16661    let mut cx = EditorTestContext::new(cx).await;
16662
16663    let diff_base = r#"
16664        use some::mod;
16665
16666        const A: u32 = 42;
16667
16668        fn main() {
16669            println!("hello");
16670
16671            println!("world");
16672        }
16673        "#
16674    .unindent();
16675
16676    // Edits are modified, removed, modified, added
16677    cx.set_state(
16678        &r#"
16679        use some::modified;
16680
16681        ˇ
16682        fn main() {
16683            println!("hello there");
16684
16685            println!("around the");
16686            println!("world");
16687        }
16688        "#
16689        .unindent(),
16690    );
16691
16692    cx.set_head_text(&diff_base);
16693    executor.run_until_parked();
16694
16695    cx.update_editor(|editor, window, cx| {
16696        //Wrap around the bottom of the buffer
16697        for _ in 0..3 {
16698            editor.go_to_next_hunk(&GoToHunk, window, cx);
16699        }
16700    });
16701
16702    cx.assert_editor_state(
16703        &r#"
16704        ˇuse some::modified;
16705
16706
16707        fn main() {
16708            println!("hello there");
16709
16710            println!("around the");
16711            println!("world");
16712        }
16713        "#
16714        .unindent(),
16715    );
16716
16717    cx.update_editor(|editor, window, cx| {
16718        //Wrap around the top of the buffer
16719        for _ in 0..2 {
16720            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16721        }
16722    });
16723
16724    cx.assert_editor_state(
16725        &r#"
16726        use some::modified;
16727
16728
16729        fn main() {
16730        ˇ    println!("hello there");
16731
16732            println!("around the");
16733            println!("world");
16734        }
16735        "#
16736        .unindent(),
16737    );
16738
16739    cx.update_editor(|editor, window, cx| {
16740        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16741    });
16742
16743    cx.assert_editor_state(
16744        &r#"
16745        use some::modified;
16746
16747        ˇ
16748        fn main() {
16749            println!("hello there");
16750
16751            println!("around the");
16752            println!("world");
16753        }
16754        "#
16755        .unindent(),
16756    );
16757
16758    cx.update_editor(|editor, window, cx| {
16759        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16760    });
16761
16762    cx.assert_editor_state(
16763        &r#"
16764        ˇuse some::modified;
16765
16766
16767        fn main() {
16768            println!("hello there");
16769
16770            println!("around the");
16771            println!("world");
16772        }
16773        "#
16774        .unindent(),
16775    );
16776
16777    cx.update_editor(|editor, window, cx| {
16778        for _ in 0..2 {
16779            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16780        }
16781    });
16782
16783    cx.assert_editor_state(
16784        &r#"
16785        use some::modified;
16786
16787
16788        fn main() {
16789        ˇ    println!("hello there");
16790
16791            println!("around the");
16792            println!("world");
16793        }
16794        "#
16795        .unindent(),
16796    );
16797
16798    cx.update_editor(|editor, window, cx| {
16799        editor.fold(&Fold, window, cx);
16800    });
16801
16802    cx.update_editor(|editor, window, cx| {
16803        editor.go_to_next_hunk(&GoToHunk, window, cx);
16804    });
16805
16806    cx.assert_editor_state(
16807        &r#"
16808        ˇuse some::modified;
16809
16810
16811        fn main() {
16812            println!("hello there");
16813
16814            println!("around the");
16815            println!("world");
16816        }
16817        "#
16818        .unindent(),
16819    );
16820}
16821
16822#[test]
16823fn test_split_words() {
16824    fn split(text: &str) -> Vec<&str> {
16825        split_words(text).collect()
16826    }
16827
16828    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16829    assert_eq!(split("hello_world"), &["hello_", "world"]);
16830    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16831    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16832    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16833    assert_eq!(split("helloworld"), &["helloworld"]);
16834
16835    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16836}
16837
16838#[gpui::test]
16839async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16840    init_test(cx, |_| {});
16841
16842    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16843    let mut assert = |before, after| {
16844        let _state_context = cx.set_state(before);
16845        cx.run_until_parked();
16846        cx.update_editor(|editor, window, cx| {
16847            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16848        });
16849        cx.run_until_parked();
16850        cx.assert_editor_state(after);
16851    };
16852
16853    // Outside bracket jumps to outside of matching bracket
16854    assert("console.logˇ(var);", "console.log(var)ˇ;");
16855    assert("console.log(var)ˇ;", "console.logˇ(var);");
16856
16857    // Inside bracket jumps to inside of matching bracket
16858    assert("console.log(ˇvar);", "console.log(varˇ);");
16859    assert("console.log(varˇ);", "console.log(ˇvar);");
16860
16861    // When outside a bracket and inside, favor jumping to the inside bracket
16862    assert(
16863        "console.log('foo', [1, 2, 3]ˇ);",
16864        "console.log(ˇ'foo', [1, 2, 3]);",
16865    );
16866    assert(
16867        "console.log(ˇ'foo', [1, 2, 3]);",
16868        "console.log('foo', [1, 2, 3]ˇ);",
16869    );
16870
16871    // Bias forward if two options are equally likely
16872    assert(
16873        "let result = curried_fun()ˇ();",
16874        "let result = curried_fun()()ˇ;",
16875    );
16876
16877    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16878    assert(
16879        indoc! {"
16880            function test() {
16881                console.log('test')ˇ
16882            }"},
16883        indoc! {"
16884            function test() {
16885                console.logˇ('test')
16886            }"},
16887    );
16888}
16889
16890#[gpui::test]
16891async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16892    init_test(cx, |_| {});
16893
16894    let fs = FakeFs::new(cx.executor());
16895    fs.insert_tree(
16896        path!("/a"),
16897        json!({
16898            "main.rs": "fn main() { let a = 5; }",
16899            "other.rs": "// Test file",
16900        }),
16901    )
16902    .await;
16903    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16904
16905    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16906    language_registry.add(Arc::new(Language::new(
16907        LanguageConfig {
16908            name: "Rust".into(),
16909            matcher: LanguageMatcher {
16910                path_suffixes: vec!["rs".to_string()],
16911                ..Default::default()
16912            },
16913            brackets: BracketPairConfig {
16914                pairs: vec![BracketPair {
16915                    start: "{".to_string(),
16916                    end: "}".to_string(),
16917                    close: true,
16918                    surround: true,
16919                    newline: true,
16920                }],
16921                disabled_scopes_by_bracket_ix: Vec::new(),
16922            },
16923            ..Default::default()
16924        },
16925        Some(tree_sitter_rust::LANGUAGE.into()),
16926    )));
16927    let mut fake_servers = language_registry.register_fake_lsp(
16928        "Rust",
16929        FakeLspAdapter {
16930            capabilities: lsp::ServerCapabilities {
16931                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16932                    first_trigger_character: "{".to_string(),
16933                    more_trigger_character: None,
16934                }),
16935                ..Default::default()
16936            },
16937            ..Default::default()
16938        },
16939    );
16940
16941    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16942
16943    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16944
16945    let worktree_id = workspace
16946        .update(cx, |workspace, _, cx| {
16947            workspace.project().update(cx, |project, cx| {
16948                project.worktrees(cx).next().unwrap().read(cx).id()
16949            })
16950        })
16951        .unwrap();
16952
16953    let buffer = project
16954        .update(cx, |project, cx| {
16955            project.open_local_buffer(path!("/a/main.rs"), cx)
16956        })
16957        .await
16958        .unwrap();
16959    let editor_handle = workspace
16960        .update(cx, |workspace, window, cx| {
16961            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
16962        })
16963        .unwrap()
16964        .await
16965        .unwrap()
16966        .downcast::<Editor>()
16967        .unwrap();
16968
16969    cx.executor().start_waiting();
16970    let fake_server = fake_servers.next().await.unwrap();
16971
16972    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
16973        |params, _| async move {
16974            assert_eq!(
16975                params.text_document_position.text_document.uri,
16976                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
16977            );
16978            assert_eq!(
16979                params.text_document_position.position,
16980                lsp::Position::new(0, 21),
16981            );
16982
16983            Ok(Some(vec![lsp::TextEdit {
16984                new_text: "]".to_string(),
16985                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16986            }]))
16987        },
16988    );
16989
16990    editor_handle.update_in(cx, |editor, window, cx| {
16991        window.focus(&editor.focus_handle(cx));
16992        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16993            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
16994        });
16995        editor.handle_input("{", window, cx);
16996    });
16997
16998    cx.executor().run_until_parked();
16999
17000    buffer.update(cx, |buffer, _| {
17001        assert_eq!(
17002            buffer.text(),
17003            "fn main() { let a = {5}; }",
17004            "No extra braces from on type formatting should appear in the buffer"
17005        )
17006    });
17007}
17008
17009#[gpui::test(iterations = 20, seeds(31))]
17010async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17011    init_test(cx, |_| {});
17012
17013    let mut cx = EditorLspTestContext::new_rust(
17014        lsp::ServerCapabilities {
17015            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17016                first_trigger_character: ".".to_string(),
17017                more_trigger_character: None,
17018            }),
17019            ..Default::default()
17020        },
17021        cx,
17022    )
17023    .await;
17024
17025    cx.update_buffer(|buffer, _| {
17026        // This causes autoindent to be async.
17027        buffer.set_sync_parse_timeout(Duration::ZERO)
17028    });
17029
17030    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17031    cx.simulate_keystroke("\n");
17032    cx.run_until_parked();
17033
17034    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17035    let mut request =
17036        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17037            let buffer_cloned = buffer_cloned.clone();
17038            async move {
17039                buffer_cloned.update(&mut cx, |buffer, _| {
17040                    assert_eq!(
17041                        buffer.text(),
17042                        "fn c() {\n    d()\n        .\n}\n",
17043                        "OnTypeFormatting should triggered after autoindent applied"
17044                    )
17045                })?;
17046
17047                Ok(Some(vec![]))
17048            }
17049        });
17050
17051    cx.simulate_keystroke(".");
17052    cx.run_until_parked();
17053
17054    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17055    assert!(request.next().await.is_some());
17056    request.close();
17057    assert!(request.next().await.is_none());
17058}
17059
17060#[gpui::test]
17061async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17062    init_test(cx, |_| {});
17063
17064    let fs = FakeFs::new(cx.executor());
17065    fs.insert_tree(
17066        path!("/a"),
17067        json!({
17068            "main.rs": "fn main() { let a = 5; }",
17069            "other.rs": "// Test file",
17070        }),
17071    )
17072    .await;
17073
17074    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17075
17076    let server_restarts = Arc::new(AtomicUsize::new(0));
17077    let closure_restarts = Arc::clone(&server_restarts);
17078    let language_server_name = "test language server";
17079    let language_name: LanguageName = "Rust".into();
17080
17081    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17082    language_registry.add(Arc::new(Language::new(
17083        LanguageConfig {
17084            name: language_name.clone(),
17085            matcher: LanguageMatcher {
17086                path_suffixes: vec!["rs".to_string()],
17087                ..Default::default()
17088            },
17089            ..Default::default()
17090        },
17091        Some(tree_sitter_rust::LANGUAGE.into()),
17092    )));
17093    let mut fake_servers = language_registry.register_fake_lsp(
17094        "Rust",
17095        FakeLspAdapter {
17096            name: language_server_name,
17097            initialization_options: Some(json!({
17098                "testOptionValue": true
17099            })),
17100            initializer: Some(Box::new(move |fake_server| {
17101                let task_restarts = Arc::clone(&closure_restarts);
17102                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17103                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17104                    futures::future::ready(Ok(()))
17105                });
17106            })),
17107            ..Default::default()
17108        },
17109    );
17110
17111    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17112    let _buffer = project
17113        .update(cx, |project, cx| {
17114            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17115        })
17116        .await
17117        .unwrap();
17118    let _fake_server = fake_servers.next().await.unwrap();
17119    update_test_language_settings(cx, |language_settings| {
17120        language_settings.languages.0.insert(
17121            language_name.clone().0,
17122            LanguageSettingsContent {
17123                tab_size: NonZeroU32::new(8),
17124                ..Default::default()
17125            },
17126        );
17127    });
17128    cx.executor().run_until_parked();
17129    assert_eq!(
17130        server_restarts.load(atomic::Ordering::Acquire),
17131        0,
17132        "Should not restart LSP server on an unrelated change"
17133    );
17134
17135    update_test_project_settings(cx, |project_settings| {
17136        project_settings.lsp.insert(
17137            "Some other server name".into(),
17138            LspSettings {
17139                binary: None,
17140                settings: None,
17141                initialization_options: Some(json!({
17142                    "some other init value": false
17143                })),
17144                enable_lsp_tasks: false,
17145                fetch: None,
17146            },
17147        );
17148    });
17149    cx.executor().run_until_parked();
17150    assert_eq!(
17151        server_restarts.load(atomic::Ordering::Acquire),
17152        0,
17153        "Should not restart LSP server on an unrelated LSP settings change"
17154    );
17155
17156    update_test_project_settings(cx, |project_settings| {
17157        project_settings.lsp.insert(
17158            language_server_name.into(),
17159            LspSettings {
17160                binary: None,
17161                settings: None,
17162                initialization_options: Some(json!({
17163                    "anotherInitValue": false
17164                })),
17165                enable_lsp_tasks: false,
17166                fetch: None,
17167            },
17168        );
17169    });
17170    cx.executor().run_until_parked();
17171    assert_eq!(
17172        server_restarts.load(atomic::Ordering::Acquire),
17173        1,
17174        "Should restart LSP server on a related LSP settings change"
17175    );
17176
17177    update_test_project_settings(cx, |project_settings| {
17178        project_settings.lsp.insert(
17179            language_server_name.into(),
17180            LspSettings {
17181                binary: None,
17182                settings: None,
17183                initialization_options: Some(json!({
17184                    "anotherInitValue": false
17185                })),
17186                enable_lsp_tasks: false,
17187                fetch: None,
17188            },
17189        );
17190    });
17191    cx.executor().run_until_parked();
17192    assert_eq!(
17193        server_restarts.load(atomic::Ordering::Acquire),
17194        1,
17195        "Should not restart LSP server on a related LSP settings change that is the same"
17196    );
17197
17198    update_test_project_settings(cx, |project_settings| {
17199        project_settings.lsp.insert(
17200            language_server_name.into(),
17201            LspSettings {
17202                binary: None,
17203                settings: None,
17204                initialization_options: None,
17205                enable_lsp_tasks: false,
17206                fetch: None,
17207            },
17208        );
17209    });
17210    cx.executor().run_until_parked();
17211    assert_eq!(
17212        server_restarts.load(atomic::Ordering::Acquire),
17213        2,
17214        "Should restart LSP server on another related LSP settings change"
17215    );
17216}
17217
17218#[gpui::test]
17219async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17220    init_test(cx, |_| {});
17221
17222    let mut cx = EditorLspTestContext::new_rust(
17223        lsp::ServerCapabilities {
17224            completion_provider: Some(lsp::CompletionOptions {
17225                trigger_characters: Some(vec![".".to_string()]),
17226                resolve_provider: Some(true),
17227                ..Default::default()
17228            }),
17229            ..Default::default()
17230        },
17231        cx,
17232    )
17233    .await;
17234
17235    cx.set_state("fn main() { let a = 2ˇ; }");
17236    cx.simulate_keystroke(".");
17237    let completion_item = lsp::CompletionItem {
17238        label: "some".into(),
17239        kind: Some(lsp::CompletionItemKind::SNIPPET),
17240        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17241        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17242            kind: lsp::MarkupKind::Markdown,
17243            value: "```rust\nSome(2)\n```".to_string(),
17244        })),
17245        deprecated: Some(false),
17246        sort_text: Some("fffffff2".to_string()),
17247        filter_text: Some("some".to_string()),
17248        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17249        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17250            range: lsp::Range {
17251                start: lsp::Position {
17252                    line: 0,
17253                    character: 22,
17254                },
17255                end: lsp::Position {
17256                    line: 0,
17257                    character: 22,
17258                },
17259            },
17260            new_text: "Some(2)".to_string(),
17261        })),
17262        additional_text_edits: Some(vec![lsp::TextEdit {
17263            range: lsp::Range {
17264                start: lsp::Position {
17265                    line: 0,
17266                    character: 20,
17267                },
17268                end: lsp::Position {
17269                    line: 0,
17270                    character: 22,
17271                },
17272            },
17273            new_text: "".to_string(),
17274        }]),
17275        ..Default::default()
17276    };
17277
17278    let closure_completion_item = completion_item.clone();
17279    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17280        let task_completion_item = closure_completion_item.clone();
17281        async move {
17282            Ok(Some(lsp::CompletionResponse::Array(vec![
17283                task_completion_item,
17284            ])))
17285        }
17286    });
17287
17288    request.next().await;
17289
17290    cx.condition(|editor, _| editor.context_menu_visible())
17291        .await;
17292    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17293        editor
17294            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17295            .unwrap()
17296    });
17297    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17298
17299    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17300        let task_completion_item = completion_item.clone();
17301        async move { Ok(task_completion_item) }
17302    })
17303    .next()
17304    .await
17305    .unwrap();
17306    apply_additional_edits.await.unwrap();
17307    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17308}
17309
17310#[gpui::test]
17311async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17312    init_test(cx, |_| {});
17313
17314    let mut cx = EditorLspTestContext::new_rust(
17315        lsp::ServerCapabilities {
17316            completion_provider: Some(lsp::CompletionOptions {
17317                trigger_characters: Some(vec![".".to_string()]),
17318                resolve_provider: Some(true),
17319                ..Default::default()
17320            }),
17321            ..Default::default()
17322        },
17323        cx,
17324    )
17325    .await;
17326
17327    cx.set_state("fn main() { let a = 2ˇ; }");
17328    cx.simulate_keystroke(".");
17329
17330    let item1 = lsp::CompletionItem {
17331        label: "method id()".to_string(),
17332        filter_text: Some("id".to_string()),
17333        detail: None,
17334        documentation: None,
17335        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17336            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17337            new_text: ".id".to_string(),
17338        })),
17339        ..lsp::CompletionItem::default()
17340    };
17341
17342    let item2 = lsp::CompletionItem {
17343        label: "other".to_string(),
17344        filter_text: Some("other".to_string()),
17345        detail: None,
17346        documentation: None,
17347        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17348            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17349            new_text: ".other".to_string(),
17350        })),
17351        ..lsp::CompletionItem::default()
17352    };
17353
17354    let item1 = item1.clone();
17355    cx.set_request_handler::<lsp::request::Completion, _, _>({
17356        let item1 = item1.clone();
17357        move |_, _, _| {
17358            let item1 = item1.clone();
17359            let item2 = item2.clone();
17360            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17361        }
17362    })
17363    .next()
17364    .await;
17365
17366    cx.condition(|editor, _| editor.context_menu_visible())
17367        .await;
17368    cx.update_editor(|editor, _, _| {
17369        let context_menu = editor.context_menu.borrow_mut();
17370        let context_menu = context_menu
17371            .as_ref()
17372            .expect("Should have the context menu deployed");
17373        match context_menu {
17374            CodeContextMenu::Completions(completions_menu) => {
17375                let completions = completions_menu.completions.borrow_mut();
17376                assert_eq!(
17377                    completions
17378                        .iter()
17379                        .map(|completion| &completion.label.text)
17380                        .collect::<Vec<_>>(),
17381                    vec!["method id()", "other"]
17382                )
17383            }
17384            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17385        }
17386    });
17387
17388    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17389        let item1 = item1.clone();
17390        move |_, item_to_resolve, _| {
17391            let item1 = item1.clone();
17392            async move {
17393                if item1 == item_to_resolve {
17394                    Ok(lsp::CompletionItem {
17395                        label: "method id()".to_string(),
17396                        filter_text: Some("id".to_string()),
17397                        detail: Some("Now resolved!".to_string()),
17398                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17399                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17400                            range: lsp::Range::new(
17401                                lsp::Position::new(0, 22),
17402                                lsp::Position::new(0, 22),
17403                            ),
17404                            new_text: ".id".to_string(),
17405                        })),
17406                        ..lsp::CompletionItem::default()
17407                    })
17408                } else {
17409                    Ok(item_to_resolve)
17410                }
17411            }
17412        }
17413    })
17414    .next()
17415    .await
17416    .unwrap();
17417    cx.run_until_parked();
17418
17419    cx.update_editor(|editor, window, cx| {
17420        editor.context_menu_next(&Default::default(), window, cx);
17421    });
17422
17423    cx.update_editor(|editor, _, _| {
17424        let context_menu = editor.context_menu.borrow_mut();
17425        let context_menu = context_menu
17426            .as_ref()
17427            .expect("Should have the context menu deployed");
17428        match context_menu {
17429            CodeContextMenu::Completions(completions_menu) => {
17430                let completions = completions_menu.completions.borrow_mut();
17431                assert_eq!(
17432                    completions
17433                        .iter()
17434                        .map(|completion| &completion.label.text)
17435                        .collect::<Vec<_>>(),
17436                    vec!["method id() Now resolved!", "other"],
17437                    "Should update first completion label, but not second as the filter text did not match."
17438                );
17439            }
17440            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17441        }
17442    });
17443}
17444
17445#[gpui::test]
17446async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17447    init_test(cx, |_| {});
17448    let mut cx = EditorLspTestContext::new_rust(
17449        lsp::ServerCapabilities {
17450            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17451            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17452            completion_provider: Some(lsp::CompletionOptions {
17453                resolve_provider: Some(true),
17454                ..Default::default()
17455            }),
17456            ..Default::default()
17457        },
17458        cx,
17459    )
17460    .await;
17461    cx.set_state(indoc! {"
17462        struct TestStruct {
17463            field: i32
17464        }
17465
17466        fn mainˇ() {
17467            let unused_var = 42;
17468            let test_struct = TestStruct { field: 42 };
17469        }
17470    "});
17471    let symbol_range = cx.lsp_range(indoc! {"
17472        struct TestStruct {
17473            field: i32
17474        }
17475
17476        «fn main»() {
17477            let unused_var = 42;
17478            let test_struct = TestStruct { field: 42 };
17479        }
17480    "});
17481    let mut hover_requests =
17482        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17483            Ok(Some(lsp::Hover {
17484                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17485                    kind: lsp::MarkupKind::Markdown,
17486                    value: "Function documentation".to_string(),
17487                }),
17488                range: Some(symbol_range),
17489            }))
17490        });
17491
17492    // Case 1: Test that code action menu hide hover popover
17493    cx.dispatch_action(Hover);
17494    hover_requests.next().await;
17495    cx.condition(|editor, _| editor.hover_state.visible()).await;
17496    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17497        move |_, _, _| async move {
17498            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17499                lsp::CodeAction {
17500                    title: "Remove unused variable".to_string(),
17501                    kind: Some(CodeActionKind::QUICKFIX),
17502                    edit: Some(lsp::WorkspaceEdit {
17503                        changes: Some(
17504                            [(
17505                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17506                                vec![lsp::TextEdit {
17507                                    range: lsp::Range::new(
17508                                        lsp::Position::new(5, 4),
17509                                        lsp::Position::new(5, 27),
17510                                    ),
17511                                    new_text: "".to_string(),
17512                                }],
17513                            )]
17514                            .into_iter()
17515                            .collect(),
17516                        ),
17517                        ..Default::default()
17518                    }),
17519                    ..Default::default()
17520                },
17521            )]))
17522        },
17523    );
17524    cx.update_editor(|editor, window, cx| {
17525        editor.toggle_code_actions(
17526            &ToggleCodeActions {
17527                deployed_from: None,
17528                quick_launch: false,
17529            },
17530            window,
17531            cx,
17532        );
17533    });
17534    code_action_requests.next().await;
17535    cx.run_until_parked();
17536    cx.condition(|editor, _| editor.context_menu_visible())
17537        .await;
17538    cx.update_editor(|editor, _, _| {
17539        assert!(
17540            !editor.hover_state.visible(),
17541            "Hover popover should be hidden when code action menu is shown"
17542        );
17543        // Hide code actions
17544        editor.context_menu.take();
17545    });
17546
17547    // Case 2: Test that code completions hide hover popover
17548    cx.dispatch_action(Hover);
17549    hover_requests.next().await;
17550    cx.condition(|editor, _| editor.hover_state.visible()).await;
17551    let counter = Arc::new(AtomicUsize::new(0));
17552    let mut completion_requests =
17553        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17554            let counter = counter.clone();
17555            async move {
17556                counter.fetch_add(1, atomic::Ordering::Release);
17557                Ok(Some(lsp::CompletionResponse::Array(vec![
17558                    lsp::CompletionItem {
17559                        label: "main".into(),
17560                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17561                        detail: Some("() -> ()".to_string()),
17562                        ..Default::default()
17563                    },
17564                    lsp::CompletionItem {
17565                        label: "TestStruct".into(),
17566                        kind: Some(lsp::CompletionItemKind::STRUCT),
17567                        detail: Some("struct TestStruct".to_string()),
17568                        ..Default::default()
17569                    },
17570                ])))
17571            }
17572        });
17573    cx.update_editor(|editor, window, cx| {
17574        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17575    });
17576    completion_requests.next().await;
17577    cx.condition(|editor, _| editor.context_menu_visible())
17578        .await;
17579    cx.update_editor(|editor, _, _| {
17580        assert!(
17581            !editor.hover_state.visible(),
17582            "Hover popover should be hidden when completion menu is shown"
17583        );
17584    });
17585}
17586
17587#[gpui::test]
17588async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17589    init_test(cx, |_| {});
17590
17591    let mut cx = EditorLspTestContext::new_rust(
17592        lsp::ServerCapabilities {
17593            completion_provider: Some(lsp::CompletionOptions {
17594                trigger_characters: Some(vec![".".to_string()]),
17595                resolve_provider: Some(true),
17596                ..Default::default()
17597            }),
17598            ..Default::default()
17599        },
17600        cx,
17601    )
17602    .await;
17603
17604    cx.set_state("fn main() { let a = 2ˇ; }");
17605    cx.simulate_keystroke(".");
17606
17607    let unresolved_item_1 = lsp::CompletionItem {
17608        label: "id".to_string(),
17609        filter_text: Some("id".to_string()),
17610        detail: None,
17611        documentation: None,
17612        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17613            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17614            new_text: ".id".to_string(),
17615        })),
17616        ..lsp::CompletionItem::default()
17617    };
17618    let resolved_item_1 = lsp::CompletionItem {
17619        additional_text_edits: Some(vec![lsp::TextEdit {
17620            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17621            new_text: "!!".to_string(),
17622        }]),
17623        ..unresolved_item_1.clone()
17624    };
17625    let unresolved_item_2 = lsp::CompletionItem {
17626        label: "other".to_string(),
17627        filter_text: Some("other".to_string()),
17628        detail: None,
17629        documentation: None,
17630        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17631            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17632            new_text: ".other".to_string(),
17633        })),
17634        ..lsp::CompletionItem::default()
17635    };
17636    let resolved_item_2 = lsp::CompletionItem {
17637        additional_text_edits: Some(vec![lsp::TextEdit {
17638            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17639            new_text: "??".to_string(),
17640        }]),
17641        ..unresolved_item_2.clone()
17642    };
17643
17644    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17645    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17646    cx.lsp
17647        .server
17648        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17649            let unresolved_item_1 = unresolved_item_1.clone();
17650            let resolved_item_1 = resolved_item_1.clone();
17651            let unresolved_item_2 = unresolved_item_2.clone();
17652            let resolved_item_2 = resolved_item_2.clone();
17653            let resolve_requests_1 = resolve_requests_1.clone();
17654            let resolve_requests_2 = resolve_requests_2.clone();
17655            move |unresolved_request, _| {
17656                let unresolved_item_1 = unresolved_item_1.clone();
17657                let resolved_item_1 = resolved_item_1.clone();
17658                let unresolved_item_2 = unresolved_item_2.clone();
17659                let resolved_item_2 = resolved_item_2.clone();
17660                let resolve_requests_1 = resolve_requests_1.clone();
17661                let resolve_requests_2 = resolve_requests_2.clone();
17662                async move {
17663                    if unresolved_request == unresolved_item_1 {
17664                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17665                        Ok(resolved_item_1.clone())
17666                    } else if unresolved_request == unresolved_item_2 {
17667                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17668                        Ok(resolved_item_2.clone())
17669                    } else {
17670                        panic!("Unexpected completion item {unresolved_request:?}")
17671                    }
17672                }
17673            }
17674        })
17675        .detach();
17676
17677    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17678        let unresolved_item_1 = unresolved_item_1.clone();
17679        let unresolved_item_2 = unresolved_item_2.clone();
17680        async move {
17681            Ok(Some(lsp::CompletionResponse::Array(vec![
17682                unresolved_item_1,
17683                unresolved_item_2,
17684            ])))
17685        }
17686    })
17687    .next()
17688    .await;
17689
17690    cx.condition(|editor, _| editor.context_menu_visible())
17691        .await;
17692    cx.update_editor(|editor, _, _| {
17693        let context_menu = editor.context_menu.borrow_mut();
17694        let context_menu = context_menu
17695            .as_ref()
17696            .expect("Should have the context menu deployed");
17697        match context_menu {
17698            CodeContextMenu::Completions(completions_menu) => {
17699                let completions = completions_menu.completions.borrow_mut();
17700                assert_eq!(
17701                    completions
17702                        .iter()
17703                        .map(|completion| &completion.label.text)
17704                        .collect::<Vec<_>>(),
17705                    vec!["id", "other"]
17706                )
17707            }
17708            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17709        }
17710    });
17711    cx.run_until_parked();
17712
17713    cx.update_editor(|editor, window, cx| {
17714        editor.context_menu_next(&ContextMenuNext, window, cx);
17715    });
17716    cx.run_until_parked();
17717    cx.update_editor(|editor, window, cx| {
17718        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17719    });
17720    cx.run_until_parked();
17721    cx.update_editor(|editor, window, cx| {
17722        editor.context_menu_next(&ContextMenuNext, window, cx);
17723    });
17724    cx.run_until_parked();
17725    cx.update_editor(|editor, window, cx| {
17726        editor
17727            .compose_completion(&ComposeCompletion::default(), window, cx)
17728            .expect("No task returned")
17729    })
17730    .await
17731    .expect("Completion failed");
17732    cx.run_until_parked();
17733
17734    cx.update_editor(|editor, _, cx| {
17735        assert_eq!(
17736            resolve_requests_1.load(atomic::Ordering::Acquire),
17737            1,
17738            "Should always resolve once despite multiple selections"
17739        );
17740        assert_eq!(
17741            resolve_requests_2.load(atomic::Ordering::Acquire),
17742            1,
17743            "Should always resolve once after multiple selections and applying the completion"
17744        );
17745        assert_eq!(
17746            editor.text(cx),
17747            "fn main() { let a = ??.other; }",
17748            "Should use resolved data when applying the completion"
17749        );
17750    });
17751}
17752
17753#[gpui::test]
17754async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17755    init_test(cx, |_| {});
17756
17757    let item_0 = lsp::CompletionItem {
17758        label: "abs".into(),
17759        insert_text: Some("abs".into()),
17760        data: Some(json!({ "very": "special"})),
17761        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17762        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17763            lsp::InsertReplaceEdit {
17764                new_text: "abs".to_string(),
17765                insert: lsp::Range::default(),
17766                replace: lsp::Range::default(),
17767            },
17768        )),
17769        ..lsp::CompletionItem::default()
17770    };
17771    let items = iter::once(item_0.clone())
17772        .chain((11..51).map(|i| lsp::CompletionItem {
17773            label: format!("item_{}", i),
17774            insert_text: Some(format!("item_{}", i)),
17775            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17776            ..lsp::CompletionItem::default()
17777        }))
17778        .collect::<Vec<_>>();
17779
17780    let default_commit_characters = vec!["?".to_string()];
17781    let default_data = json!({ "default": "data"});
17782    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17783    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17784    let default_edit_range = lsp::Range {
17785        start: lsp::Position {
17786            line: 0,
17787            character: 5,
17788        },
17789        end: lsp::Position {
17790            line: 0,
17791            character: 5,
17792        },
17793    };
17794
17795    let mut cx = EditorLspTestContext::new_rust(
17796        lsp::ServerCapabilities {
17797            completion_provider: Some(lsp::CompletionOptions {
17798                trigger_characters: Some(vec![".".to_string()]),
17799                resolve_provider: Some(true),
17800                ..Default::default()
17801            }),
17802            ..Default::default()
17803        },
17804        cx,
17805    )
17806    .await;
17807
17808    cx.set_state("fn main() { let a = 2ˇ; }");
17809    cx.simulate_keystroke(".");
17810
17811    let completion_data = default_data.clone();
17812    let completion_characters = default_commit_characters.clone();
17813    let completion_items = items.clone();
17814    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17815        let default_data = completion_data.clone();
17816        let default_commit_characters = completion_characters.clone();
17817        let items = completion_items.clone();
17818        async move {
17819            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17820                items,
17821                item_defaults: Some(lsp::CompletionListItemDefaults {
17822                    data: Some(default_data.clone()),
17823                    commit_characters: Some(default_commit_characters.clone()),
17824                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17825                        default_edit_range,
17826                    )),
17827                    insert_text_format: Some(default_insert_text_format),
17828                    insert_text_mode: Some(default_insert_text_mode),
17829                }),
17830                ..lsp::CompletionList::default()
17831            })))
17832        }
17833    })
17834    .next()
17835    .await;
17836
17837    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17838    cx.lsp
17839        .server
17840        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17841            let closure_resolved_items = resolved_items.clone();
17842            move |item_to_resolve, _| {
17843                let closure_resolved_items = closure_resolved_items.clone();
17844                async move {
17845                    closure_resolved_items.lock().push(item_to_resolve.clone());
17846                    Ok(item_to_resolve)
17847                }
17848            }
17849        })
17850        .detach();
17851
17852    cx.condition(|editor, _| editor.context_menu_visible())
17853        .await;
17854    cx.run_until_parked();
17855    cx.update_editor(|editor, _, _| {
17856        let menu = editor.context_menu.borrow_mut();
17857        match menu.as_ref().expect("should have the completions menu") {
17858            CodeContextMenu::Completions(completions_menu) => {
17859                assert_eq!(
17860                    completions_menu
17861                        .entries
17862                        .borrow()
17863                        .iter()
17864                        .map(|mat| mat.string.clone())
17865                        .collect::<Vec<String>>(),
17866                    items
17867                        .iter()
17868                        .map(|completion| completion.label.clone())
17869                        .collect::<Vec<String>>()
17870                );
17871            }
17872            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17873        }
17874    });
17875    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17876    // with 4 from the end.
17877    assert_eq!(
17878        *resolved_items.lock(),
17879        [&items[0..16], &items[items.len() - 4..items.len()]]
17880            .concat()
17881            .iter()
17882            .cloned()
17883            .map(|mut item| {
17884                if item.data.is_none() {
17885                    item.data = Some(default_data.clone());
17886                }
17887                item
17888            })
17889            .collect::<Vec<lsp::CompletionItem>>(),
17890        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17891    );
17892    resolved_items.lock().clear();
17893
17894    cx.update_editor(|editor, window, cx| {
17895        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17896    });
17897    cx.run_until_parked();
17898    // Completions that have already been resolved are skipped.
17899    assert_eq!(
17900        *resolved_items.lock(),
17901        items[items.len() - 17..items.len() - 4]
17902            .iter()
17903            .cloned()
17904            .map(|mut item| {
17905                if item.data.is_none() {
17906                    item.data = Some(default_data.clone());
17907                }
17908                item
17909            })
17910            .collect::<Vec<lsp::CompletionItem>>()
17911    );
17912    resolved_items.lock().clear();
17913}
17914
17915#[gpui::test]
17916async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17917    init_test(cx, |_| {});
17918
17919    let mut cx = EditorLspTestContext::new(
17920        Language::new(
17921            LanguageConfig {
17922                matcher: LanguageMatcher {
17923                    path_suffixes: vec!["jsx".into()],
17924                    ..Default::default()
17925                },
17926                overrides: [(
17927                    "element".into(),
17928                    LanguageConfigOverride {
17929                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
17930                        ..Default::default()
17931                    },
17932                )]
17933                .into_iter()
17934                .collect(),
17935                ..Default::default()
17936            },
17937            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17938        )
17939        .with_override_query("(jsx_self_closing_element) @element")
17940        .unwrap(),
17941        lsp::ServerCapabilities {
17942            completion_provider: Some(lsp::CompletionOptions {
17943                trigger_characters: Some(vec![":".to_string()]),
17944                ..Default::default()
17945            }),
17946            ..Default::default()
17947        },
17948        cx,
17949    )
17950    .await;
17951
17952    cx.lsp
17953        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17954            Ok(Some(lsp::CompletionResponse::Array(vec![
17955                lsp::CompletionItem {
17956                    label: "bg-blue".into(),
17957                    ..Default::default()
17958                },
17959                lsp::CompletionItem {
17960                    label: "bg-red".into(),
17961                    ..Default::default()
17962                },
17963                lsp::CompletionItem {
17964                    label: "bg-yellow".into(),
17965                    ..Default::default()
17966                },
17967            ])))
17968        });
17969
17970    cx.set_state(r#"<p class="bgˇ" />"#);
17971
17972    // Trigger completion when typing a dash, because the dash is an extra
17973    // word character in the 'element' scope, which contains the cursor.
17974    cx.simulate_keystroke("-");
17975    cx.executor().run_until_parked();
17976    cx.update_editor(|editor, _, _| {
17977        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17978        {
17979            assert_eq!(
17980                completion_menu_entries(menu),
17981                &["bg-blue", "bg-red", "bg-yellow"]
17982            );
17983        } else {
17984            panic!("expected completion menu to be open");
17985        }
17986    });
17987
17988    cx.simulate_keystroke("l");
17989    cx.executor().run_until_parked();
17990    cx.update_editor(|editor, _, _| {
17991        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17992        {
17993            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
17994        } else {
17995            panic!("expected completion menu to be open");
17996        }
17997    });
17998
17999    // When filtering completions, consider the character after the '-' to
18000    // be the start of a subword.
18001    cx.set_state(r#"<p class="yelˇ" />"#);
18002    cx.simulate_keystroke("l");
18003    cx.executor().run_until_parked();
18004    cx.update_editor(|editor, _, _| {
18005        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18006        {
18007            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18008        } else {
18009            panic!("expected completion menu to be open");
18010        }
18011    });
18012}
18013
18014fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18015    let entries = menu.entries.borrow();
18016    entries.iter().map(|mat| mat.string.clone()).collect()
18017}
18018
18019#[gpui::test]
18020async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18021    init_test(cx, |settings| {
18022        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
18023            Formatter::Prettier,
18024        )))
18025    });
18026
18027    let fs = FakeFs::new(cx.executor());
18028    fs.insert_file(path!("/file.ts"), Default::default()).await;
18029
18030    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18031    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18032
18033    language_registry.add(Arc::new(Language::new(
18034        LanguageConfig {
18035            name: "TypeScript".into(),
18036            matcher: LanguageMatcher {
18037                path_suffixes: vec!["ts".to_string()],
18038                ..Default::default()
18039            },
18040            ..Default::default()
18041        },
18042        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18043    )));
18044    update_test_language_settings(cx, |settings| {
18045        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18046    });
18047
18048    let test_plugin = "test_plugin";
18049    let _ = language_registry.register_fake_lsp(
18050        "TypeScript",
18051        FakeLspAdapter {
18052            prettier_plugins: vec![test_plugin],
18053            ..Default::default()
18054        },
18055    );
18056
18057    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18058    let buffer = project
18059        .update(cx, |project, cx| {
18060            project.open_local_buffer(path!("/file.ts"), cx)
18061        })
18062        .await
18063        .unwrap();
18064
18065    let buffer_text = "one\ntwo\nthree\n";
18066    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18067    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18068    editor.update_in(cx, |editor, window, cx| {
18069        editor.set_text(buffer_text, window, cx)
18070    });
18071
18072    editor
18073        .update_in(cx, |editor, window, cx| {
18074            editor.perform_format(
18075                project.clone(),
18076                FormatTrigger::Manual,
18077                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18078                window,
18079                cx,
18080            )
18081        })
18082        .unwrap()
18083        .await;
18084    assert_eq!(
18085        editor.update(cx, |editor, cx| editor.text(cx)),
18086        buffer_text.to_string() + prettier_format_suffix,
18087        "Test prettier formatting was not applied to the original buffer text",
18088    );
18089
18090    update_test_language_settings(cx, |settings| {
18091        settings.defaults.formatter = Some(SelectedFormatter::Auto)
18092    });
18093    let format = editor.update_in(cx, |editor, window, cx| {
18094        editor.perform_format(
18095            project.clone(),
18096            FormatTrigger::Manual,
18097            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18098            window,
18099            cx,
18100        )
18101    });
18102    format.await.unwrap();
18103    assert_eq!(
18104        editor.update(cx, |editor, cx| editor.text(cx)),
18105        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18106        "Autoformatting (via test prettier) was not applied to the original buffer text",
18107    );
18108}
18109
18110#[gpui::test]
18111async fn test_addition_reverts(cx: &mut TestAppContext) {
18112    init_test(cx, |_| {});
18113    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18114    let base_text = indoc! {r#"
18115        struct Row;
18116        struct Row1;
18117        struct Row2;
18118
18119        struct Row4;
18120        struct Row5;
18121        struct Row6;
18122
18123        struct Row8;
18124        struct Row9;
18125        struct Row10;"#};
18126
18127    // When addition hunks are not adjacent to carets, no hunk revert is performed
18128    assert_hunk_revert(
18129        indoc! {r#"struct Row;
18130                   struct Row1;
18131                   struct Row1.1;
18132                   struct Row1.2;
18133                   struct Row2;ˇ
18134
18135                   struct Row4;
18136                   struct Row5;
18137                   struct Row6;
18138
18139                   struct Row8;
18140                   ˇstruct Row9;
18141                   struct Row9.1;
18142                   struct Row9.2;
18143                   struct Row9.3;
18144                   struct Row10;"#},
18145        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18146        indoc! {r#"struct Row;
18147                   struct Row1;
18148                   struct Row1.1;
18149                   struct Row1.2;
18150                   struct Row2;ˇ
18151
18152                   struct Row4;
18153                   struct Row5;
18154                   struct Row6;
18155
18156                   struct Row8;
18157                   ˇstruct Row9;
18158                   struct Row9.1;
18159                   struct Row9.2;
18160                   struct Row9.3;
18161                   struct Row10;"#},
18162        base_text,
18163        &mut cx,
18164    );
18165    // Same for selections
18166    assert_hunk_revert(
18167        indoc! {r#"struct Row;
18168                   struct Row1;
18169                   struct Row2;
18170                   struct Row2.1;
18171                   struct Row2.2;
18172                   «ˇ
18173                   struct Row4;
18174                   struct» Row5;
18175                   «struct Row6;
18176                   ˇ»
18177                   struct Row9.1;
18178                   struct Row9.2;
18179                   struct Row9.3;
18180                   struct Row8;
18181                   struct Row9;
18182                   struct Row10;"#},
18183        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18184        indoc! {r#"struct Row;
18185                   struct Row1;
18186                   struct Row2;
18187                   struct Row2.1;
18188                   struct Row2.2;
18189                   «ˇ
18190                   struct Row4;
18191                   struct» Row5;
18192                   «struct Row6;
18193                   ˇ»
18194                   struct Row9.1;
18195                   struct Row9.2;
18196                   struct Row9.3;
18197                   struct Row8;
18198                   struct Row9;
18199                   struct Row10;"#},
18200        base_text,
18201        &mut cx,
18202    );
18203
18204    // When carets and selections intersect the addition hunks, those are reverted.
18205    // Adjacent carets got merged.
18206    assert_hunk_revert(
18207        indoc! {r#"struct Row;
18208                   ˇ// something on the top
18209                   struct Row1;
18210                   struct Row2;
18211                   struct Roˇw3.1;
18212                   struct Row2.2;
18213                   struct Row2.3;ˇ
18214
18215                   struct Row4;
18216                   struct ˇRow5.1;
18217                   struct Row5.2;
18218                   struct «Rowˇ»5.3;
18219                   struct Row5;
18220                   struct Row6;
18221                   ˇ
18222                   struct Row9.1;
18223                   struct «Rowˇ»9.2;
18224                   struct «ˇRow»9.3;
18225                   struct Row8;
18226                   struct Row9;
18227                   «ˇ// something on bottom»
18228                   struct Row10;"#},
18229        vec![
18230            DiffHunkStatusKind::Added,
18231            DiffHunkStatusKind::Added,
18232            DiffHunkStatusKind::Added,
18233            DiffHunkStatusKind::Added,
18234            DiffHunkStatusKind::Added,
18235        ],
18236        indoc! {r#"struct Row;
18237                   ˇstruct Row1;
18238                   struct Row2;
18239                   ˇ
18240                   struct Row4;
18241                   ˇstruct Row5;
18242                   struct Row6;
18243                   ˇ
18244                   ˇstruct Row8;
18245                   struct Row9;
18246                   ˇstruct Row10;"#},
18247        base_text,
18248        &mut cx,
18249    );
18250}
18251
18252#[gpui::test]
18253async fn test_modification_reverts(cx: &mut TestAppContext) {
18254    init_test(cx, |_| {});
18255    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18256    let base_text = indoc! {r#"
18257        struct Row;
18258        struct Row1;
18259        struct Row2;
18260
18261        struct Row4;
18262        struct Row5;
18263        struct Row6;
18264
18265        struct Row8;
18266        struct Row9;
18267        struct Row10;"#};
18268
18269    // Modification hunks behave the same as the addition ones.
18270    assert_hunk_revert(
18271        indoc! {r#"struct Row;
18272                   struct Row1;
18273                   struct Row33;
18274                   ˇ
18275                   struct Row4;
18276                   struct Row5;
18277                   struct Row6;
18278                   ˇ
18279                   struct Row99;
18280                   struct Row9;
18281                   struct Row10;"#},
18282        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18283        indoc! {r#"struct Row;
18284                   struct Row1;
18285                   struct Row33;
18286                   ˇ
18287                   struct Row4;
18288                   struct Row5;
18289                   struct Row6;
18290                   ˇ
18291                   struct Row99;
18292                   struct Row9;
18293                   struct Row10;"#},
18294        base_text,
18295        &mut cx,
18296    );
18297    assert_hunk_revert(
18298        indoc! {r#"struct Row;
18299                   struct Row1;
18300                   struct Row33;
18301                   «ˇ
18302                   struct Row4;
18303                   struct» Row5;
18304                   «struct Row6;
18305                   ˇ»
18306                   struct Row99;
18307                   struct Row9;
18308                   struct Row10;"#},
18309        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18310        indoc! {r#"struct Row;
18311                   struct Row1;
18312                   struct Row33;
18313                   «ˇ
18314                   struct Row4;
18315                   struct» Row5;
18316                   «struct Row6;
18317                   ˇ»
18318                   struct Row99;
18319                   struct Row9;
18320                   struct Row10;"#},
18321        base_text,
18322        &mut cx,
18323    );
18324
18325    assert_hunk_revert(
18326        indoc! {r#"ˇstruct Row1.1;
18327                   struct Row1;
18328                   «ˇstr»uct Row22;
18329
18330                   struct ˇRow44;
18331                   struct Row5;
18332                   struct «Rˇ»ow66;ˇ
18333
18334                   «struˇ»ct Row88;
18335                   struct Row9;
18336                   struct Row1011;ˇ"#},
18337        vec![
18338            DiffHunkStatusKind::Modified,
18339            DiffHunkStatusKind::Modified,
18340            DiffHunkStatusKind::Modified,
18341            DiffHunkStatusKind::Modified,
18342            DiffHunkStatusKind::Modified,
18343            DiffHunkStatusKind::Modified,
18344        ],
18345        indoc! {r#"struct Row;
18346                   ˇstruct Row1;
18347                   struct Row2;
18348                   ˇ
18349                   struct Row4;
18350                   ˇstruct Row5;
18351                   struct Row6;
18352                   ˇ
18353                   struct Row8;
18354                   ˇstruct Row9;
18355                   struct Row10;ˇ"#},
18356        base_text,
18357        &mut cx,
18358    );
18359}
18360
18361#[gpui::test]
18362async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18363    init_test(cx, |_| {});
18364    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18365    let base_text = indoc! {r#"
18366        one
18367
18368        two
18369        three
18370        "#};
18371
18372    cx.set_head_text(base_text);
18373    cx.set_state("\nˇ\n");
18374    cx.executor().run_until_parked();
18375    cx.update_editor(|editor, _window, cx| {
18376        editor.expand_selected_diff_hunks(cx);
18377    });
18378    cx.executor().run_until_parked();
18379    cx.update_editor(|editor, window, cx| {
18380        editor.backspace(&Default::default(), window, cx);
18381    });
18382    cx.run_until_parked();
18383    cx.assert_state_with_diff(
18384        indoc! {r#"
18385
18386        - two
18387        - threeˇ
18388        +
18389        "#}
18390        .to_string(),
18391    );
18392}
18393
18394#[gpui::test]
18395async fn test_deletion_reverts(cx: &mut TestAppContext) {
18396    init_test(cx, |_| {});
18397    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18398    let base_text = indoc! {r#"struct Row;
18399struct Row1;
18400struct Row2;
18401
18402struct Row4;
18403struct Row5;
18404struct Row6;
18405
18406struct Row8;
18407struct Row9;
18408struct Row10;"#};
18409
18410    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18411    assert_hunk_revert(
18412        indoc! {r#"struct Row;
18413                   struct Row2;
18414
18415                   ˇstruct Row4;
18416                   struct Row5;
18417                   struct Row6;
18418                   ˇ
18419                   struct Row8;
18420                   struct Row10;"#},
18421        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18422        indoc! {r#"struct Row;
18423                   struct Row2;
18424
18425                   ˇstruct Row4;
18426                   struct Row5;
18427                   struct Row6;
18428                   ˇ
18429                   struct Row8;
18430                   struct Row10;"#},
18431        base_text,
18432        &mut cx,
18433    );
18434    assert_hunk_revert(
18435        indoc! {r#"struct Row;
18436                   struct Row2;
18437
18438                   «ˇstruct Row4;
18439                   struct» Row5;
18440                   «struct Row6;
18441                   ˇ»
18442                   struct Row8;
18443                   struct Row10;"#},
18444        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18445        indoc! {r#"struct Row;
18446                   struct Row2;
18447
18448                   «ˇstruct Row4;
18449                   struct» Row5;
18450                   «struct Row6;
18451                   ˇ»
18452                   struct Row8;
18453                   struct Row10;"#},
18454        base_text,
18455        &mut cx,
18456    );
18457
18458    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18459    assert_hunk_revert(
18460        indoc! {r#"struct Row;
18461                   ˇstruct Row2;
18462
18463                   struct Row4;
18464                   struct Row5;
18465                   struct Row6;
18466
18467                   struct Row8;ˇ
18468                   struct Row10;"#},
18469        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18470        indoc! {r#"struct Row;
18471                   struct Row1;
18472                   ˇstruct Row2;
18473
18474                   struct Row4;
18475                   struct Row5;
18476                   struct Row6;
18477
18478                   struct Row8;ˇ
18479                   struct Row9;
18480                   struct Row10;"#},
18481        base_text,
18482        &mut cx,
18483    );
18484    assert_hunk_revert(
18485        indoc! {r#"struct Row;
18486                   struct Row2«ˇ;
18487                   struct Row4;
18488                   struct» Row5;
18489                   «struct Row6;
18490
18491                   struct Row8;ˇ»
18492                   struct Row10;"#},
18493        vec![
18494            DiffHunkStatusKind::Deleted,
18495            DiffHunkStatusKind::Deleted,
18496            DiffHunkStatusKind::Deleted,
18497        ],
18498        indoc! {r#"struct Row;
18499                   struct Row1;
18500                   struct Row2«ˇ;
18501
18502                   struct Row4;
18503                   struct» Row5;
18504                   «struct Row6;
18505
18506                   struct Row8;ˇ»
18507                   struct Row9;
18508                   struct Row10;"#},
18509        base_text,
18510        &mut cx,
18511    );
18512}
18513
18514#[gpui::test]
18515async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18516    init_test(cx, |_| {});
18517
18518    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18519    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18520    let base_text_3 =
18521        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18522
18523    let text_1 = edit_first_char_of_every_line(base_text_1);
18524    let text_2 = edit_first_char_of_every_line(base_text_2);
18525    let text_3 = edit_first_char_of_every_line(base_text_3);
18526
18527    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18528    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18529    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18530
18531    let multibuffer = cx.new(|cx| {
18532        let mut multibuffer = MultiBuffer::new(ReadWrite);
18533        multibuffer.push_excerpts(
18534            buffer_1.clone(),
18535            [
18536                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18537                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18538                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18539            ],
18540            cx,
18541        );
18542        multibuffer.push_excerpts(
18543            buffer_2.clone(),
18544            [
18545                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18546                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18547                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18548            ],
18549            cx,
18550        );
18551        multibuffer.push_excerpts(
18552            buffer_3.clone(),
18553            [
18554                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18555                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18556                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18557            ],
18558            cx,
18559        );
18560        multibuffer
18561    });
18562
18563    let fs = FakeFs::new(cx.executor());
18564    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18565    let (editor, cx) = cx
18566        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18567    editor.update_in(cx, |editor, _window, cx| {
18568        for (buffer, diff_base) in [
18569            (buffer_1.clone(), base_text_1),
18570            (buffer_2.clone(), base_text_2),
18571            (buffer_3.clone(), base_text_3),
18572        ] {
18573            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18574            editor
18575                .buffer
18576                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18577        }
18578    });
18579    cx.executor().run_until_parked();
18580
18581    editor.update_in(cx, |editor, window, cx| {
18582        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}");
18583        editor.select_all(&SelectAll, window, cx);
18584        editor.git_restore(&Default::default(), window, cx);
18585    });
18586    cx.executor().run_until_parked();
18587
18588    // When all ranges are selected, all buffer hunks are reverted.
18589    editor.update(cx, |editor, cx| {
18590        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");
18591    });
18592    buffer_1.update(cx, |buffer, _| {
18593        assert_eq!(buffer.text(), base_text_1);
18594    });
18595    buffer_2.update(cx, |buffer, _| {
18596        assert_eq!(buffer.text(), base_text_2);
18597    });
18598    buffer_3.update(cx, |buffer, _| {
18599        assert_eq!(buffer.text(), base_text_3);
18600    });
18601
18602    editor.update_in(cx, |editor, window, cx| {
18603        editor.undo(&Default::default(), window, cx);
18604    });
18605
18606    editor.update_in(cx, |editor, window, cx| {
18607        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18608            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18609        });
18610        editor.git_restore(&Default::default(), window, cx);
18611    });
18612
18613    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18614    // but not affect buffer_2 and its related excerpts.
18615    editor.update(cx, |editor, cx| {
18616        assert_eq!(
18617            editor.text(cx),
18618            "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}"
18619        );
18620    });
18621    buffer_1.update(cx, |buffer, _| {
18622        assert_eq!(buffer.text(), base_text_1);
18623    });
18624    buffer_2.update(cx, |buffer, _| {
18625        assert_eq!(
18626            buffer.text(),
18627            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18628        );
18629    });
18630    buffer_3.update(cx, |buffer, _| {
18631        assert_eq!(
18632            buffer.text(),
18633            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18634        );
18635    });
18636
18637    fn edit_first_char_of_every_line(text: &str) -> String {
18638        text.split('\n')
18639            .map(|line| format!("X{}", &line[1..]))
18640            .collect::<Vec<_>>()
18641            .join("\n")
18642    }
18643}
18644
18645#[gpui::test]
18646async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18647    init_test(cx, |_| {});
18648
18649    let cols = 4;
18650    let rows = 10;
18651    let sample_text_1 = sample_text(rows, cols, 'a');
18652    assert_eq!(
18653        sample_text_1,
18654        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18655    );
18656    let sample_text_2 = sample_text(rows, cols, 'l');
18657    assert_eq!(
18658        sample_text_2,
18659        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18660    );
18661    let sample_text_3 = sample_text(rows, cols, 'v');
18662    assert_eq!(
18663        sample_text_3,
18664        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18665    );
18666
18667    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18668    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18669    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18670
18671    let multi_buffer = cx.new(|cx| {
18672        let mut multibuffer = MultiBuffer::new(ReadWrite);
18673        multibuffer.push_excerpts(
18674            buffer_1.clone(),
18675            [
18676                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18677                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18678                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18679            ],
18680            cx,
18681        );
18682        multibuffer.push_excerpts(
18683            buffer_2.clone(),
18684            [
18685                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18686                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18687                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18688            ],
18689            cx,
18690        );
18691        multibuffer.push_excerpts(
18692            buffer_3.clone(),
18693            [
18694                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18695                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18696                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18697            ],
18698            cx,
18699        );
18700        multibuffer
18701    });
18702
18703    let fs = FakeFs::new(cx.executor());
18704    fs.insert_tree(
18705        "/a",
18706        json!({
18707            "main.rs": sample_text_1,
18708            "other.rs": sample_text_2,
18709            "lib.rs": sample_text_3,
18710        }),
18711    )
18712    .await;
18713    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18714    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18715    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18716    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18717        Editor::new(
18718            EditorMode::full(),
18719            multi_buffer,
18720            Some(project.clone()),
18721            window,
18722            cx,
18723        )
18724    });
18725    let multibuffer_item_id = workspace
18726        .update(cx, |workspace, window, cx| {
18727            assert!(
18728                workspace.active_item(cx).is_none(),
18729                "active item should be None before the first item is added"
18730            );
18731            workspace.add_item_to_active_pane(
18732                Box::new(multi_buffer_editor.clone()),
18733                None,
18734                true,
18735                window,
18736                cx,
18737            );
18738            let active_item = workspace
18739                .active_item(cx)
18740                .expect("should have an active item after adding the multi buffer");
18741            assert!(
18742                !active_item.is_singleton(cx),
18743                "A multi buffer was expected to active after adding"
18744            );
18745            active_item.item_id()
18746        })
18747        .unwrap();
18748    cx.executor().run_until_parked();
18749
18750    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18751        editor.change_selections(
18752            SelectionEffects::scroll(Autoscroll::Next),
18753            window,
18754            cx,
18755            |s| s.select_ranges(Some(1..2)),
18756        );
18757        editor.open_excerpts(&OpenExcerpts, window, cx);
18758    });
18759    cx.executor().run_until_parked();
18760    let first_item_id = workspace
18761        .update(cx, |workspace, window, cx| {
18762            let active_item = workspace
18763                .active_item(cx)
18764                .expect("should have an active item after navigating into the 1st buffer");
18765            let first_item_id = active_item.item_id();
18766            assert_ne!(
18767                first_item_id, multibuffer_item_id,
18768                "Should navigate into the 1st buffer and activate it"
18769            );
18770            assert!(
18771                active_item.is_singleton(cx),
18772                "New active item should be a singleton buffer"
18773            );
18774            assert_eq!(
18775                active_item
18776                    .act_as::<Editor>(cx)
18777                    .expect("should have navigated into an editor for the 1st buffer")
18778                    .read(cx)
18779                    .text(cx),
18780                sample_text_1
18781            );
18782
18783            workspace
18784                .go_back(workspace.active_pane().downgrade(), window, cx)
18785                .detach_and_log_err(cx);
18786
18787            first_item_id
18788        })
18789        .unwrap();
18790    cx.executor().run_until_parked();
18791    workspace
18792        .update(cx, |workspace, _, cx| {
18793            let active_item = workspace
18794                .active_item(cx)
18795                .expect("should have an active item after navigating back");
18796            assert_eq!(
18797                active_item.item_id(),
18798                multibuffer_item_id,
18799                "Should navigate back to the multi buffer"
18800            );
18801            assert!(!active_item.is_singleton(cx));
18802        })
18803        .unwrap();
18804
18805    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18806        editor.change_selections(
18807            SelectionEffects::scroll(Autoscroll::Next),
18808            window,
18809            cx,
18810            |s| s.select_ranges(Some(39..40)),
18811        );
18812        editor.open_excerpts(&OpenExcerpts, window, cx);
18813    });
18814    cx.executor().run_until_parked();
18815    let second_item_id = workspace
18816        .update(cx, |workspace, window, cx| {
18817            let active_item = workspace
18818                .active_item(cx)
18819                .expect("should have an active item after navigating into the 2nd buffer");
18820            let second_item_id = active_item.item_id();
18821            assert_ne!(
18822                second_item_id, multibuffer_item_id,
18823                "Should navigate away from the multibuffer"
18824            );
18825            assert_ne!(
18826                second_item_id, first_item_id,
18827                "Should navigate into the 2nd buffer and activate it"
18828            );
18829            assert!(
18830                active_item.is_singleton(cx),
18831                "New active item should be a singleton buffer"
18832            );
18833            assert_eq!(
18834                active_item
18835                    .act_as::<Editor>(cx)
18836                    .expect("should have navigated into an editor")
18837                    .read(cx)
18838                    .text(cx),
18839                sample_text_2
18840            );
18841
18842            workspace
18843                .go_back(workspace.active_pane().downgrade(), window, cx)
18844                .detach_and_log_err(cx);
18845
18846            second_item_id
18847        })
18848        .unwrap();
18849    cx.executor().run_until_parked();
18850    workspace
18851        .update(cx, |workspace, _, cx| {
18852            let active_item = workspace
18853                .active_item(cx)
18854                .expect("should have an active item after navigating back from the 2nd buffer");
18855            assert_eq!(
18856                active_item.item_id(),
18857                multibuffer_item_id,
18858                "Should navigate back from the 2nd buffer to the multi buffer"
18859            );
18860            assert!(!active_item.is_singleton(cx));
18861        })
18862        .unwrap();
18863
18864    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18865        editor.change_selections(
18866            SelectionEffects::scroll(Autoscroll::Next),
18867            window,
18868            cx,
18869            |s| s.select_ranges(Some(70..70)),
18870        );
18871        editor.open_excerpts(&OpenExcerpts, window, cx);
18872    });
18873    cx.executor().run_until_parked();
18874    workspace
18875        .update(cx, |workspace, window, cx| {
18876            let active_item = workspace
18877                .active_item(cx)
18878                .expect("should have an active item after navigating into the 3rd buffer");
18879            let third_item_id = active_item.item_id();
18880            assert_ne!(
18881                third_item_id, multibuffer_item_id,
18882                "Should navigate into the 3rd buffer and activate it"
18883            );
18884            assert_ne!(third_item_id, first_item_id);
18885            assert_ne!(third_item_id, second_item_id);
18886            assert!(
18887                active_item.is_singleton(cx),
18888                "New active item should be a singleton buffer"
18889            );
18890            assert_eq!(
18891                active_item
18892                    .act_as::<Editor>(cx)
18893                    .expect("should have navigated into an editor")
18894                    .read(cx)
18895                    .text(cx),
18896                sample_text_3
18897            );
18898
18899            workspace
18900                .go_back(workspace.active_pane().downgrade(), window, cx)
18901                .detach_and_log_err(cx);
18902        })
18903        .unwrap();
18904    cx.executor().run_until_parked();
18905    workspace
18906        .update(cx, |workspace, _, cx| {
18907            let active_item = workspace
18908                .active_item(cx)
18909                .expect("should have an active item after navigating back from the 3rd buffer");
18910            assert_eq!(
18911                active_item.item_id(),
18912                multibuffer_item_id,
18913                "Should navigate back from the 3rd buffer to the multi buffer"
18914            );
18915            assert!(!active_item.is_singleton(cx));
18916        })
18917        .unwrap();
18918}
18919
18920#[gpui::test]
18921async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18922    init_test(cx, |_| {});
18923
18924    let mut cx = EditorTestContext::new(cx).await;
18925
18926    let diff_base = r#"
18927        use some::mod;
18928
18929        const A: u32 = 42;
18930
18931        fn main() {
18932            println!("hello");
18933
18934            println!("world");
18935        }
18936        "#
18937    .unindent();
18938
18939    cx.set_state(
18940        &r#"
18941        use some::modified;
18942
18943        ˇ
18944        fn main() {
18945            println!("hello there");
18946
18947            println!("around the");
18948            println!("world");
18949        }
18950        "#
18951        .unindent(),
18952    );
18953
18954    cx.set_head_text(&diff_base);
18955    executor.run_until_parked();
18956
18957    cx.update_editor(|editor, window, cx| {
18958        editor.go_to_next_hunk(&GoToHunk, window, cx);
18959        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18960    });
18961    executor.run_until_parked();
18962    cx.assert_state_with_diff(
18963        r#"
18964          use some::modified;
18965
18966
18967          fn main() {
18968        -     println!("hello");
18969        + ˇ    println!("hello there");
18970
18971              println!("around the");
18972              println!("world");
18973          }
18974        "#
18975        .unindent(),
18976    );
18977
18978    cx.update_editor(|editor, window, cx| {
18979        for _ in 0..2 {
18980            editor.go_to_next_hunk(&GoToHunk, window, cx);
18981            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18982        }
18983    });
18984    executor.run_until_parked();
18985    cx.assert_state_with_diff(
18986        r#"
18987        - use some::mod;
18988        + ˇuse some::modified;
18989
18990
18991          fn main() {
18992        -     println!("hello");
18993        +     println!("hello there");
18994
18995        +     println!("around the");
18996              println!("world");
18997          }
18998        "#
18999        .unindent(),
19000    );
19001
19002    cx.update_editor(|editor, window, cx| {
19003        editor.go_to_next_hunk(&GoToHunk, window, cx);
19004        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19005    });
19006    executor.run_until_parked();
19007    cx.assert_state_with_diff(
19008        r#"
19009        - use some::mod;
19010        + use some::modified;
19011
19012        - const A: u32 = 42;
19013          ˇ
19014          fn main() {
19015        -     println!("hello");
19016        +     println!("hello there");
19017
19018        +     println!("around the");
19019              println!("world");
19020          }
19021        "#
19022        .unindent(),
19023    );
19024
19025    cx.update_editor(|editor, window, cx| {
19026        editor.cancel(&Cancel, window, cx);
19027    });
19028
19029    cx.assert_state_with_diff(
19030        r#"
19031          use some::modified;
19032
19033          ˇ
19034          fn main() {
19035              println!("hello there");
19036
19037              println!("around the");
19038              println!("world");
19039          }
19040        "#
19041        .unindent(),
19042    );
19043}
19044
19045#[gpui::test]
19046async fn test_diff_base_change_with_expanded_diff_hunks(
19047    executor: BackgroundExecutor,
19048    cx: &mut TestAppContext,
19049) {
19050    init_test(cx, |_| {});
19051
19052    let mut cx = EditorTestContext::new(cx).await;
19053
19054    let diff_base = r#"
19055        use some::mod1;
19056        use some::mod2;
19057
19058        const A: u32 = 42;
19059        const B: u32 = 42;
19060        const C: u32 = 42;
19061
19062        fn main() {
19063            println!("hello");
19064
19065            println!("world");
19066        }
19067        "#
19068    .unindent();
19069
19070    cx.set_state(
19071        &r#"
19072        use some::mod2;
19073
19074        const A: u32 = 42;
19075        const C: u32 = 42;
19076
19077        fn main(ˇ) {
19078            //println!("hello");
19079
19080            println!("world");
19081            //
19082            //
19083        }
19084        "#
19085        .unindent(),
19086    );
19087
19088    cx.set_head_text(&diff_base);
19089    executor.run_until_parked();
19090
19091    cx.update_editor(|editor, window, cx| {
19092        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19093    });
19094    executor.run_until_parked();
19095    cx.assert_state_with_diff(
19096        r#"
19097        - use some::mod1;
19098          use some::mod2;
19099
19100          const A: u32 = 42;
19101        - const B: u32 = 42;
19102          const C: u32 = 42;
19103
19104          fn main(ˇ) {
19105        -     println!("hello");
19106        +     //println!("hello");
19107
19108              println!("world");
19109        +     //
19110        +     //
19111          }
19112        "#
19113        .unindent(),
19114    );
19115
19116    cx.set_head_text("new diff base!");
19117    executor.run_until_parked();
19118    cx.assert_state_with_diff(
19119        r#"
19120        - new diff base!
19121        + use some::mod2;
19122        +
19123        + const A: u32 = 42;
19124        + const C: u32 = 42;
19125        +
19126        + fn main(ˇ) {
19127        +     //println!("hello");
19128        +
19129        +     println!("world");
19130        +     //
19131        +     //
19132        + }
19133        "#
19134        .unindent(),
19135    );
19136}
19137
19138#[gpui::test]
19139async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19140    init_test(cx, |_| {});
19141
19142    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19143    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19144    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19145    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19146    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19147    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19148
19149    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19150    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19151    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19152
19153    let multi_buffer = cx.new(|cx| {
19154        let mut multibuffer = MultiBuffer::new(ReadWrite);
19155        multibuffer.push_excerpts(
19156            buffer_1.clone(),
19157            [
19158                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19159                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19160                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19161            ],
19162            cx,
19163        );
19164        multibuffer.push_excerpts(
19165            buffer_2.clone(),
19166            [
19167                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19168                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19169                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19170            ],
19171            cx,
19172        );
19173        multibuffer.push_excerpts(
19174            buffer_3.clone(),
19175            [
19176                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19177                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19178                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19179            ],
19180            cx,
19181        );
19182        multibuffer
19183    });
19184
19185    let editor =
19186        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19187    editor
19188        .update(cx, |editor, _window, cx| {
19189            for (buffer, diff_base) in [
19190                (buffer_1.clone(), file_1_old),
19191                (buffer_2.clone(), file_2_old),
19192                (buffer_3.clone(), file_3_old),
19193            ] {
19194                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19195                editor
19196                    .buffer
19197                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19198            }
19199        })
19200        .unwrap();
19201
19202    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19203    cx.run_until_parked();
19204
19205    cx.assert_editor_state(
19206        &"
19207            ˇaaa
19208            ccc
19209            ddd
19210
19211            ggg
19212            hhh
19213
19214
19215            lll
19216            mmm
19217            NNN
19218
19219            qqq
19220            rrr
19221
19222            uuu
19223            111
19224            222
19225            333
19226
19227            666
19228            777
19229
19230            000
19231            !!!"
19232        .unindent(),
19233    );
19234
19235    cx.update_editor(|editor, window, cx| {
19236        editor.select_all(&SelectAll, window, cx);
19237        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19238    });
19239    cx.executor().run_until_parked();
19240
19241    cx.assert_state_with_diff(
19242        "
19243            «aaa
19244          - bbb
19245            ccc
19246            ddd
19247
19248            ggg
19249            hhh
19250
19251
19252            lll
19253            mmm
19254          - nnn
19255          + NNN
19256
19257            qqq
19258            rrr
19259
19260            uuu
19261            111
19262            222
19263            333
19264
19265          + 666
19266            777
19267
19268            000
19269            !!!ˇ»"
19270            .unindent(),
19271    );
19272}
19273
19274#[gpui::test]
19275async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19276    init_test(cx, |_| {});
19277
19278    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19279    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19280
19281    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19282    let multi_buffer = cx.new(|cx| {
19283        let mut multibuffer = MultiBuffer::new(ReadWrite);
19284        multibuffer.push_excerpts(
19285            buffer.clone(),
19286            [
19287                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19288                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19289                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19290            ],
19291            cx,
19292        );
19293        multibuffer
19294    });
19295
19296    let editor =
19297        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19298    editor
19299        .update(cx, |editor, _window, cx| {
19300            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19301            editor
19302                .buffer
19303                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19304        })
19305        .unwrap();
19306
19307    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19308    cx.run_until_parked();
19309
19310    cx.update_editor(|editor, window, cx| {
19311        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19312    });
19313    cx.executor().run_until_parked();
19314
19315    // When the start of a hunk coincides with the start of its excerpt,
19316    // the hunk is expanded. When the start of a hunk is earlier than
19317    // the start of its excerpt, the hunk is not expanded.
19318    cx.assert_state_with_diff(
19319        "
19320            ˇaaa
19321          - bbb
19322          + BBB
19323
19324          - ddd
19325          - eee
19326          + DDD
19327          + EEE
19328            fff
19329
19330            iii
19331        "
19332        .unindent(),
19333    );
19334}
19335
19336#[gpui::test]
19337async fn test_edits_around_expanded_insertion_hunks(
19338    executor: BackgroundExecutor,
19339    cx: &mut TestAppContext,
19340) {
19341    init_test(cx, |_| {});
19342
19343    let mut cx = EditorTestContext::new(cx).await;
19344
19345    let diff_base = r#"
19346        use some::mod1;
19347        use some::mod2;
19348
19349        const A: u32 = 42;
19350
19351        fn main() {
19352            println!("hello");
19353
19354            println!("world");
19355        }
19356        "#
19357    .unindent();
19358    executor.run_until_parked();
19359    cx.set_state(
19360        &r#"
19361        use some::mod1;
19362        use some::mod2;
19363
19364        const A: u32 = 42;
19365        const B: u32 = 42;
19366        const C: u32 = 42;
19367        ˇ
19368
19369        fn main() {
19370            println!("hello");
19371
19372            println!("world");
19373        }
19374        "#
19375        .unindent(),
19376    );
19377
19378    cx.set_head_text(&diff_base);
19379    executor.run_until_parked();
19380
19381    cx.update_editor(|editor, window, cx| {
19382        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19383    });
19384    executor.run_until_parked();
19385
19386    cx.assert_state_with_diff(
19387        r#"
19388        use some::mod1;
19389        use some::mod2;
19390
19391        const A: u32 = 42;
19392      + const B: u32 = 42;
19393      + const C: u32 = 42;
19394      + ˇ
19395
19396        fn main() {
19397            println!("hello");
19398
19399            println!("world");
19400        }
19401      "#
19402        .unindent(),
19403    );
19404
19405    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19406    executor.run_until_parked();
19407
19408    cx.assert_state_with_diff(
19409        r#"
19410        use some::mod1;
19411        use some::mod2;
19412
19413        const A: u32 = 42;
19414      + const B: u32 = 42;
19415      + const C: u32 = 42;
19416      + const D: u32 = 42;
19417      + ˇ
19418
19419        fn main() {
19420            println!("hello");
19421
19422            println!("world");
19423        }
19424      "#
19425        .unindent(),
19426    );
19427
19428    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19429    executor.run_until_parked();
19430
19431    cx.assert_state_with_diff(
19432        r#"
19433        use some::mod1;
19434        use some::mod2;
19435
19436        const A: u32 = 42;
19437      + const B: u32 = 42;
19438      + const C: u32 = 42;
19439      + const D: u32 = 42;
19440      + const E: u32 = 42;
19441      + ˇ
19442
19443        fn main() {
19444            println!("hello");
19445
19446            println!("world");
19447        }
19448      "#
19449        .unindent(),
19450    );
19451
19452    cx.update_editor(|editor, window, cx| {
19453        editor.delete_line(&DeleteLine, window, cx);
19454    });
19455    executor.run_until_parked();
19456
19457    cx.assert_state_with_diff(
19458        r#"
19459        use some::mod1;
19460        use some::mod2;
19461
19462        const A: u32 = 42;
19463      + const B: u32 = 42;
19464      + const C: u32 = 42;
19465      + const D: u32 = 42;
19466      + const E: u32 = 42;
19467        ˇ
19468        fn main() {
19469            println!("hello");
19470
19471            println!("world");
19472        }
19473      "#
19474        .unindent(),
19475    );
19476
19477    cx.update_editor(|editor, window, cx| {
19478        editor.move_up(&MoveUp, window, cx);
19479        editor.delete_line(&DeleteLine, window, cx);
19480        editor.move_up(&MoveUp, window, cx);
19481        editor.delete_line(&DeleteLine, window, cx);
19482        editor.move_up(&MoveUp, window, cx);
19483        editor.delete_line(&DeleteLine, window, cx);
19484    });
19485    executor.run_until_parked();
19486    cx.assert_state_with_diff(
19487        r#"
19488        use some::mod1;
19489        use some::mod2;
19490
19491        const A: u32 = 42;
19492      + const B: u32 = 42;
19493        ˇ
19494        fn main() {
19495            println!("hello");
19496
19497            println!("world");
19498        }
19499      "#
19500        .unindent(),
19501    );
19502
19503    cx.update_editor(|editor, window, cx| {
19504        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19505        editor.delete_line(&DeleteLine, window, cx);
19506    });
19507    executor.run_until_parked();
19508    cx.assert_state_with_diff(
19509        r#"
19510        ˇ
19511        fn main() {
19512            println!("hello");
19513
19514            println!("world");
19515        }
19516      "#
19517        .unindent(),
19518    );
19519}
19520
19521#[gpui::test]
19522async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19523    init_test(cx, |_| {});
19524
19525    let mut cx = EditorTestContext::new(cx).await;
19526    cx.set_head_text(indoc! { "
19527        one
19528        two
19529        three
19530        four
19531        five
19532        "
19533    });
19534    cx.set_state(indoc! { "
19535        one
19536        ˇthree
19537        five
19538    "});
19539    cx.run_until_parked();
19540    cx.update_editor(|editor, window, cx| {
19541        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19542    });
19543    cx.assert_state_with_diff(
19544        indoc! { "
19545        one
19546      - two
19547        ˇthree
19548      - four
19549        five
19550    "}
19551        .to_string(),
19552    );
19553    cx.update_editor(|editor, window, cx| {
19554        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19555    });
19556
19557    cx.assert_state_with_diff(
19558        indoc! { "
19559        one
19560        ˇthree
19561        five
19562    "}
19563        .to_string(),
19564    );
19565
19566    cx.set_state(indoc! { "
19567        one
19568        ˇTWO
19569        three
19570        four
19571        five
19572    "});
19573    cx.run_until_parked();
19574    cx.update_editor(|editor, window, cx| {
19575        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19576    });
19577
19578    cx.assert_state_with_diff(
19579        indoc! { "
19580            one
19581          - two
19582          + ˇTWO
19583            three
19584            four
19585            five
19586        "}
19587        .to_string(),
19588    );
19589    cx.update_editor(|editor, window, cx| {
19590        editor.move_up(&Default::default(), window, cx);
19591        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19592    });
19593    cx.assert_state_with_diff(
19594        indoc! { "
19595            one
19596            ˇTWO
19597            three
19598            four
19599            five
19600        "}
19601        .to_string(),
19602    );
19603}
19604
19605#[gpui::test]
19606async fn test_edits_around_expanded_deletion_hunks(
19607    executor: BackgroundExecutor,
19608    cx: &mut TestAppContext,
19609) {
19610    init_test(cx, |_| {});
19611
19612    let mut cx = EditorTestContext::new(cx).await;
19613
19614    let diff_base = r#"
19615        use some::mod1;
19616        use some::mod2;
19617
19618        const A: u32 = 42;
19619        const B: u32 = 42;
19620        const C: u32 = 42;
19621
19622
19623        fn main() {
19624            println!("hello");
19625
19626            println!("world");
19627        }
19628    "#
19629    .unindent();
19630    executor.run_until_parked();
19631    cx.set_state(
19632        &r#"
19633        use some::mod1;
19634        use some::mod2;
19635
19636        ˇconst B: u32 = 42;
19637        const C: u32 = 42;
19638
19639
19640        fn main() {
19641            println!("hello");
19642
19643            println!("world");
19644        }
19645        "#
19646        .unindent(),
19647    );
19648
19649    cx.set_head_text(&diff_base);
19650    executor.run_until_parked();
19651
19652    cx.update_editor(|editor, window, cx| {
19653        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19654    });
19655    executor.run_until_parked();
19656
19657    cx.assert_state_with_diff(
19658        r#"
19659        use some::mod1;
19660        use some::mod2;
19661
19662      - const A: u32 = 42;
19663        ˇconst B: u32 = 42;
19664        const C: u32 = 42;
19665
19666
19667        fn main() {
19668            println!("hello");
19669
19670            println!("world");
19671        }
19672      "#
19673        .unindent(),
19674    );
19675
19676    cx.update_editor(|editor, window, cx| {
19677        editor.delete_line(&DeleteLine, window, cx);
19678    });
19679    executor.run_until_parked();
19680    cx.assert_state_with_diff(
19681        r#"
19682        use some::mod1;
19683        use some::mod2;
19684
19685      - const A: u32 = 42;
19686      - const B: u32 = 42;
19687        ˇconst C: u32 = 42;
19688
19689
19690        fn main() {
19691            println!("hello");
19692
19693            println!("world");
19694        }
19695      "#
19696        .unindent(),
19697    );
19698
19699    cx.update_editor(|editor, window, cx| {
19700        editor.delete_line(&DeleteLine, window, cx);
19701    });
19702    executor.run_until_parked();
19703    cx.assert_state_with_diff(
19704        r#"
19705        use some::mod1;
19706        use some::mod2;
19707
19708      - const A: u32 = 42;
19709      - const B: u32 = 42;
19710      - const C: u32 = 42;
19711        ˇ
19712
19713        fn main() {
19714            println!("hello");
19715
19716            println!("world");
19717        }
19718      "#
19719        .unindent(),
19720    );
19721
19722    cx.update_editor(|editor, window, cx| {
19723        editor.handle_input("replacement", window, cx);
19724    });
19725    executor.run_until_parked();
19726    cx.assert_state_with_diff(
19727        r#"
19728        use some::mod1;
19729        use some::mod2;
19730
19731      - const A: u32 = 42;
19732      - const B: u32 = 42;
19733      - const C: u32 = 42;
19734      -
19735      + replacementˇ
19736
19737        fn main() {
19738            println!("hello");
19739
19740            println!("world");
19741        }
19742      "#
19743        .unindent(),
19744    );
19745}
19746
19747#[gpui::test]
19748async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19749    init_test(cx, |_| {});
19750
19751    let mut cx = EditorTestContext::new(cx).await;
19752
19753    let base_text = r#"
19754        one
19755        two
19756        three
19757        four
19758        five
19759    "#
19760    .unindent();
19761    executor.run_until_parked();
19762    cx.set_state(
19763        &r#"
19764        one
19765        two
19766        fˇour
19767        five
19768        "#
19769        .unindent(),
19770    );
19771
19772    cx.set_head_text(&base_text);
19773    executor.run_until_parked();
19774
19775    cx.update_editor(|editor, window, cx| {
19776        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19777    });
19778    executor.run_until_parked();
19779
19780    cx.assert_state_with_diff(
19781        r#"
19782          one
19783          two
19784        - three
19785          fˇour
19786          five
19787        "#
19788        .unindent(),
19789    );
19790
19791    cx.update_editor(|editor, window, cx| {
19792        editor.backspace(&Backspace, window, cx);
19793        editor.backspace(&Backspace, window, cx);
19794    });
19795    executor.run_until_parked();
19796    cx.assert_state_with_diff(
19797        r#"
19798          one
19799          two
19800        - threeˇ
19801        - four
19802        + our
19803          five
19804        "#
19805        .unindent(),
19806    );
19807}
19808
19809#[gpui::test]
19810async fn test_edit_after_expanded_modification_hunk(
19811    executor: BackgroundExecutor,
19812    cx: &mut TestAppContext,
19813) {
19814    init_test(cx, |_| {});
19815
19816    let mut cx = EditorTestContext::new(cx).await;
19817
19818    let diff_base = r#"
19819        use some::mod1;
19820        use some::mod2;
19821
19822        const A: u32 = 42;
19823        const B: u32 = 42;
19824        const C: u32 = 42;
19825        const D: u32 = 42;
19826
19827
19828        fn main() {
19829            println!("hello");
19830
19831            println!("world");
19832        }"#
19833    .unindent();
19834
19835    cx.set_state(
19836        &r#"
19837        use some::mod1;
19838        use some::mod2;
19839
19840        const A: u32 = 42;
19841        const B: u32 = 42;
19842        const C: u32 = 43ˇ
19843        const D: u32 = 42;
19844
19845
19846        fn main() {
19847            println!("hello");
19848
19849            println!("world");
19850        }"#
19851        .unindent(),
19852    );
19853
19854    cx.set_head_text(&diff_base);
19855    executor.run_until_parked();
19856    cx.update_editor(|editor, window, cx| {
19857        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19858    });
19859    executor.run_until_parked();
19860
19861    cx.assert_state_with_diff(
19862        r#"
19863        use some::mod1;
19864        use some::mod2;
19865
19866        const A: u32 = 42;
19867        const B: u32 = 42;
19868      - const C: u32 = 42;
19869      + const C: u32 = 43ˇ
19870        const D: u32 = 42;
19871
19872
19873        fn main() {
19874            println!("hello");
19875
19876            println!("world");
19877        }"#
19878        .unindent(),
19879    );
19880
19881    cx.update_editor(|editor, window, cx| {
19882        editor.handle_input("\nnew_line\n", window, cx);
19883    });
19884    executor.run_until_parked();
19885
19886    cx.assert_state_with_diff(
19887        r#"
19888        use some::mod1;
19889        use some::mod2;
19890
19891        const A: u32 = 42;
19892        const B: u32 = 42;
19893      - const C: u32 = 42;
19894      + const C: u32 = 43
19895      + new_line
19896      + ˇ
19897        const D: u32 = 42;
19898
19899
19900        fn main() {
19901            println!("hello");
19902
19903            println!("world");
19904        }"#
19905        .unindent(),
19906    );
19907}
19908
19909#[gpui::test]
19910async fn test_stage_and_unstage_added_file_hunk(
19911    executor: BackgroundExecutor,
19912    cx: &mut TestAppContext,
19913) {
19914    init_test(cx, |_| {});
19915
19916    let mut cx = EditorTestContext::new(cx).await;
19917    cx.update_editor(|editor, _, cx| {
19918        editor.set_expand_all_diff_hunks(cx);
19919    });
19920
19921    let working_copy = r#"
19922            ˇfn main() {
19923                println!("hello, world!");
19924            }
19925        "#
19926    .unindent();
19927
19928    cx.set_state(&working_copy);
19929    executor.run_until_parked();
19930
19931    cx.assert_state_with_diff(
19932        r#"
19933            + ˇfn main() {
19934            +     println!("hello, world!");
19935            + }
19936        "#
19937        .unindent(),
19938    );
19939    cx.assert_index_text(None);
19940
19941    cx.update_editor(|editor, window, cx| {
19942        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19943    });
19944    executor.run_until_parked();
19945    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
19946    cx.assert_state_with_diff(
19947        r#"
19948            + ˇfn main() {
19949            +     println!("hello, world!");
19950            + }
19951        "#
19952        .unindent(),
19953    );
19954
19955    cx.update_editor(|editor, window, cx| {
19956        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19957    });
19958    executor.run_until_parked();
19959    cx.assert_index_text(None);
19960}
19961
19962async fn setup_indent_guides_editor(
19963    text: &str,
19964    cx: &mut TestAppContext,
19965) -> (BufferId, EditorTestContext) {
19966    init_test(cx, |_| {});
19967
19968    let mut cx = EditorTestContext::new(cx).await;
19969
19970    let buffer_id = cx.update_editor(|editor, window, cx| {
19971        editor.set_text(text, window, cx);
19972        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
19973
19974        buffer_ids[0]
19975    });
19976
19977    (buffer_id, cx)
19978}
19979
19980fn assert_indent_guides(
19981    range: Range<u32>,
19982    expected: Vec<IndentGuide>,
19983    active_indices: Option<Vec<usize>>,
19984    cx: &mut EditorTestContext,
19985) {
19986    let indent_guides = cx.update_editor(|editor, window, cx| {
19987        let snapshot = editor.snapshot(window, cx).display_snapshot;
19988        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
19989            editor,
19990            MultiBufferRow(range.start)..MultiBufferRow(range.end),
19991            true,
19992            &snapshot,
19993            cx,
19994        );
19995
19996        indent_guides.sort_by(|a, b| {
19997            a.depth.cmp(&b.depth).then(
19998                a.start_row
19999                    .cmp(&b.start_row)
20000                    .then(a.end_row.cmp(&b.end_row)),
20001            )
20002        });
20003        indent_guides
20004    });
20005
20006    if let Some(expected) = active_indices {
20007        let active_indices = cx.update_editor(|editor, window, cx| {
20008            let snapshot = editor.snapshot(window, cx).display_snapshot;
20009            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20010        });
20011
20012        assert_eq!(
20013            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20014            expected,
20015            "Active indent guide indices do not match"
20016        );
20017    }
20018
20019    assert_eq!(indent_guides, expected, "Indent guides do not match");
20020}
20021
20022fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20023    IndentGuide {
20024        buffer_id,
20025        start_row: MultiBufferRow(start_row),
20026        end_row: MultiBufferRow(end_row),
20027        depth,
20028        tab_size: 4,
20029        settings: IndentGuideSettings {
20030            enabled: true,
20031            line_width: 1,
20032            active_line_width: 1,
20033            coloring: IndentGuideColoring::default(),
20034            background_coloring: IndentGuideBackgroundColoring::default(),
20035        },
20036    }
20037}
20038
20039#[gpui::test]
20040async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20041    let (buffer_id, mut cx) = setup_indent_guides_editor(
20042        &"
20043        fn main() {
20044            let a = 1;
20045        }"
20046        .unindent(),
20047        cx,
20048    )
20049    .await;
20050
20051    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20052}
20053
20054#[gpui::test]
20055async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20056    let (buffer_id, mut cx) = setup_indent_guides_editor(
20057        &"
20058        fn main() {
20059            let a = 1;
20060            let b = 2;
20061        }"
20062        .unindent(),
20063        cx,
20064    )
20065    .await;
20066
20067    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20068}
20069
20070#[gpui::test]
20071async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20072    let (buffer_id, mut cx) = setup_indent_guides_editor(
20073        &"
20074        fn main() {
20075            let a = 1;
20076            if a == 3 {
20077                let b = 2;
20078            } else {
20079                let c = 3;
20080            }
20081        }"
20082        .unindent(),
20083        cx,
20084    )
20085    .await;
20086
20087    assert_indent_guides(
20088        0..8,
20089        vec![
20090            indent_guide(buffer_id, 1, 6, 0),
20091            indent_guide(buffer_id, 3, 3, 1),
20092            indent_guide(buffer_id, 5, 5, 1),
20093        ],
20094        None,
20095        &mut cx,
20096    );
20097}
20098
20099#[gpui::test]
20100async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20101    let (buffer_id, mut cx) = setup_indent_guides_editor(
20102        &"
20103        fn main() {
20104            let a = 1;
20105                let b = 2;
20106            let c = 3;
20107        }"
20108        .unindent(),
20109        cx,
20110    )
20111    .await;
20112
20113    assert_indent_guides(
20114        0..5,
20115        vec![
20116            indent_guide(buffer_id, 1, 3, 0),
20117            indent_guide(buffer_id, 2, 2, 1),
20118        ],
20119        None,
20120        &mut cx,
20121    );
20122}
20123
20124#[gpui::test]
20125async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20126    let (buffer_id, mut cx) = setup_indent_guides_editor(
20127        &"
20128        fn main() {
20129            let a = 1;
20130
20131            let c = 3;
20132        }"
20133        .unindent(),
20134        cx,
20135    )
20136    .await;
20137
20138    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20139}
20140
20141#[gpui::test]
20142async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20143    let (buffer_id, mut cx) = setup_indent_guides_editor(
20144        &"
20145        fn main() {
20146            let a = 1;
20147
20148            let c = 3;
20149
20150            if a == 3 {
20151                let b = 2;
20152            } else {
20153                let c = 3;
20154            }
20155        }"
20156        .unindent(),
20157        cx,
20158    )
20159    .await;
20160
20161    assert_indent_guides(
20162        0..11,
20163        vec![
20164            indent_guide(buffer_id, 1, 9, 0),
20165            indent_guide(buffer_id, 6, 6, 1),
20166            indent_guide(buffer_id, 8, 8, 1),
20167        ],
20168        None,
20169        &mut cx,
20170    );
20171}
20172
20173#[gpui::test]
20174async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20175    let (buffer_id, mut cx) = setup_indent_guides_editor(
20176        &"
20177        fn main() {
20178            let a = 1;
20179
20180            let c = 3;
20181
20182            if a == 3 {
20183                let b = 2;
20184            } else {
20185                let c = 3;
20186            }
20187        }"
20188        .unindent(),
20189        cx,
20190    )
20191    .await;
20192
20193    assert_indent_guides(
20194        1..11,
20195        vec![
20196            indent_guide(buffer_id, 1, 9, 0),
20197            indent_guide(buffer_id, 6, 6, 1),
20198            indent_guide(buffer_id, 8, 8, 1),
20199        ],
20200        None,
20201        &mut cx,
20202    );
20203}
20204
20205#[gpui::test]
20206async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20207    let (buffer_id, mut cx) = setup_indent_guides_editor(
20208        &"
20209        fn main() {
20210            let a = 1;
20211
20212            let c = 3;
20213
20214            if a == 3 {
20215                let b = 2;
20216            } else {
20217                let c = 3;
20218            }
20219        }"
20220        .unindent(),
20221        cx,
20222    )
20223    .await;
20224
20225    assert_indent_guides(
20226        1..10,
20227        vec![
20228            indent_guide(buffer_id, 1, 9, 0),
20229            indent_guide(buffer_id, 6, 6, 1),
20230            indent_guide(buffer_id, 8, 8, 1),
20231        ],
20232        None,
20233        &mut cx,
20234    );
20235}
20236
20237#[gpui::test]
20238async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20239    let (buffer_id, mut cx) = setup_indent_guides_editor(
20240        &"
20241        fn main() {
20242            if a {
20243                b(
20244                    c,
20245                    d,
20246                )
20247            } else {
20248                e(
20249                    f
20250                )
20251            }
20252        }"
20253        .unindent(),
20254        cx,
20255    )
20256    .await;
20257
20258    assert_indent_guides(
20259        0..11,
20260        vec![
20261            indent_guide(buffer_id, 1, 10, 0),
20262            indent_guide(buffer_id, 2, 5, 1),
20263            indent_guide(buffer_id, 7, 9, 1),
20264            indent_guide(buffer_id, 3, 4, 2),
20265            indent_guide(buffer_id, 8, 8, 2),
20266        ],
20267        None,
20268        &mut cx,
20269    );
20270
20271    cx.update_editor(|editor, window, cx| {
20272        editor.fold_at(MultiBufferRow(2), window, cx);
20273        assert_eq!(
20274            editor.display_text(cx),
20275            "
20276            fn main() {
20277                if a {
20278                    b(⋯
20279                    )
20280                } else {
20281                    e(
20282                        f
20283                    )
20284                }
20285            }"
20286            .unindent()
20287        );
20288    });
20289
20290    assert_indent_guides(
20291        0..11,
20292        vec![
20293            indent_guide(buffer_id, 1, 10, 0),
20294            indent_guide(buffer_id, 2, 5, 1),
20295            indent_guide(buffer_id, 7, 9, 1),
20296            indent_guide(buffer_id, 8, 8, 2),
20297        ],
20298        None,
20299        &mut cx,
20300    );
20301}
20302
20303#[gpui::test]
20304async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20305    let (buffer_id, mut cx) = setup_indent_guides_editor(
20306        &"
20307        block1
20308            block2
20309                block3
20310                    block4
20311            block2
20312        block1
20313        block1"
20314            .unindent(),
20315        cx,
20316    )
20317    .await;
20318
20319    assert_indent_guides(
20320        1..10,
20321        vec![
20322            indent_guide(buffer_id, 1, 4, 0),
20323            indent_guide(buffer_id, 2, 3, 1),
20324            indent_guide(buffer_id, 3, 3, 2),
20325        ],
20326        None,
20327        &mut cx,
20328    );
20329}
20330
20331#[gpui::test]
20332async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20333    let (buffer_id, mut cx) = setup_indent_guides_editor(
20334        &"
20335        block1
20336            block2
20337                block3
20338
20339        block1
20340        block1"
20341            .unindent(),
20342        cx,
20343    )
20344    .await;
20345
20346    assert_indent_guides(
20347        0..6,
20348        vec![
20349            indent_guide(buffer_id, 1, 2, 0),
20350            indent_guide(buffer_id, 2, 2, 1),
20351        ],
20352        None,
20353        &mut cx,
20354    );
20355}
20356
20357#[gpui::test]
20358async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20359    let (buffer_id, mut cx) = setup_indent_guides_editor(
20360        &"
20361        function component() {
20362        \treturn (
20363        \t\t\t
20364        \t\t<div>
20365        \t\t\t<abc></abc>
20366        \t\t</div>
20367        \t)
20368        }"
20369        .unindent(),
20370        cx,
20371    )
20372    .await;
20373
20374    assert_indent_guides(
20375        0..8,
20376        vec![
20377            indent_guide(buffer_id, 1, 6, 0),
20378            indent_guide(buffer_id, 2, 5, 1),
20379            indent_guide(buffer_id, 4, 4, 2),
20380        ],
20381        None,
20382        &mut cx,
20383    );
20384}
20385
20386#[gpui::test]
20387async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20388    let (buffer_id, mut cx) = setup_indent_guides_editor(
20389        &"
20390        function component() {
20391        \treturn (
20392        \t
20393        \t\t<div>
20394        \t\t\t<abc></abc>
20395        \t\t</div>
20396        \t)
20397        }"
20398        .unindent(),
20399        cx,
20400    )
20401    .await;
20402
20403    assert_indent_guides(
20404        0..8,
20405        vec![
20406            indent_guide(buffer_id, 1, 6, 0),
20407            indent_guide(buffer_id, 2, 5, 1),
20408            indent_guide(buffer_id, 4, 4, 2),
20409        ],
20410        None,
20411        &mut cx,
20412    );
20413}
20414
20415#[gpui::test]
20416async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20417    let (buffer_id, mut cx) = setup_indent_guides_editor(
20418        &"
20419        block1
20420
20421
20422
20423            block2
20424        "
20425        .unindent(),
20426        cx,
20427    )
20428    .await;
20429
20430    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20431}
20432
20433#[gpui::test]
20434async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20435    let (buffer_id, mut cx) = setup_indent_guides_editor(
20436        &"
20437        def a:
20438        \tb = 3
20439        \tif True:
20440        \t\tc = 4
20441        \t\td = 5
20442        \tprint(b)
20443        "
20444        .unindent(),
20445        cx,
20446    )
20447    .await;
20448
20449    assert_indent_guides(
20450        0..6,
20451        vec![
20452            indent_guide(buffer_id, 1, 5, 0),
20453            indent_guide(buffer_id, 3, 4, 1),
20454        ],
20455        None,
20456        &mut cx,
20457    );
20458}
20459
20460#[gpui::test]
20461async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20462    let (buffer_id, mut cx) = setup_indent_guides_editor(
20463        &"
20464    fn main() {
20465        let a = 1;
20466    }"
20467        .unindent(),
20468        cx,
20469    )
20470    .await;
20471
20472    cx.update_editor(|editor, window, cx| {
20473        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20474            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20475        });
20476    });
20477
20478    assert_indent_guides(
20479        0..3,
20480        vec![indent_guide(buffer_id, 1, 1, 0)],
20481        Some(vec![0]),
20482        &mut cx,
20483    );
20484}
20485
20486#[gpui::test]
20487async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20488    let (buffer_id, mut cx) = setup_indent_guides_editor(
20489        &"
20490    fn main() {
20491        if 1 == 2 {
20492            let a = 1;
20493        }
20494    }"
20495        .unindent(),
20496        cx,
20497    )
20498    .await;
20499
20500    cx.update_editor(|editor, window, cx| {
20501        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20502            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20503        });
20504    });
20505
20506    assert_indent_guides(
20507        0..4,
20508        vec![
20509            indent_guide(buffer_id, 1, 3, 0),
20510            indent_guide(buffer_id, 2, 2, 1),
20511        ],
20512        Some(vec![1]),
20513        &mut cx,
20514    );
20515
20516    cx.update_editor(|editor, window, cx| {
20517        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20518            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20519        });
20520    });
20521
20522    assert_indent_guides(
20523        0..4,
20524        vec![
20525            indent_guide(buffer_id, 1, 3, 0),
20526            indent_guide(buffer_id, 2, 2, 1),
20527        ],
20528        Some(vec![1]),
20529        &mut cx,
20530    );
20531
20532    cx.update_editor(|editor, window, cx| {
20533        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20534            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20535        });
20536    });
20537
20538    assert_indent_guides(
20539        0..4,
20540        vec![
20541            indent_guide(buffer_id, 1, 3, 0),
20542            indent_guide(buffer_id, 2, 2, 1),
20543        ],
20544        Some(vec![0]),
20545        &mut cx,
20546    );
20547}
20548
20549#[gpui::test]
20550async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20551    let (buffer_id, mut cx) = setup_indent_guides_editor(
20552        &"
20553    fn main() {
20554        let a = 1;
20555
20556        let b = 2;
20557    }"
20558        .unindent(),
20559        cx,
20560    )
20561    .await;
20562
20563    cx.update_editor(|editor, window, cx| {
20564        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20565            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20566        });
20567    });
20568
20569    assert_indent_guides(
20570        0..5,
20571        vec![indent_guide(buffer_id, 1, 3, 0)],
20572        Some(vec![0]),
20573        &mut cx,
20574    );
20575}
20576
20577#[gpui::test]
20578async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20579    let (buffer_id, mut cx) = setup_indent_guides_editor(
20580        &"
20581    def m:
20582        a = 1
20583        pass"
20584            .unindent(),
20585        cx,
20586    )
20587    .await;
20588
20589    cx.update_editor(|editor, window, cx| {
20590        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20591            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20592        });
20593    });
20594
20595    assert_indent_guides(
20596        0..3,
20597        vec![indent_guide(buffer_id, 1, 2, 0)],
20598        Some(vec![0]),
20599        &mut cx,
20600    );
20601}
20602
20603#[gpui::test]
20604async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20605    init_test(cx, |_| {});
20606    let mut cx = EditorTestContext::new(cx).await;
20607    let text = indoc! {
20608        "
20609        impl A {
20610            fn b() {
20611                0;
20612                3;
20613                5;
20614                6;
20615                7;
20616            }
20617        }
20618        "
20619    };
20620    let base_text = indoc! {
20621        "
20622        impl A {
20623            fn b() {
20624                0;
20625                1;
20626                2;
20627                3;
20628                4;
20629            }
20630            fn c() {
20631                5;
20632                6;
20633                7;
20634            }
20635        }
20636        "
20637    };
20638
20639    cx.update_editor(|editor, window, cx| {
20640        editor.set_text(text, window, cx);
20641
20642        editor.buffer().update(cx, |multibuffer, cx| {
20643            let buffer = multibuffer.as_singleton().unwrap();
20644            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20645
20646            multibuffer.set_all_diff_hunks_expanded(cx);
20647            multibuffer.add_diff(diff, cx);
20648
20649            buffer.read(cx).remote_id()
20650        })
20651    });
20652    cx.run_until_parked();
20653
20654    cx.assert_state_with_diff(
20655        indoc! { "
20656          impl A {
20657              fn b() {
20658                  0;
20659        -         1;
20660        -         2;
20661                  3;
20662        -         4;
20663        -     }
20664        -     fn c() {
20665                  5;
20666                  6;
20667                  7;
20668              }
20669          }
20670          ˇ"
20671        }
20672        .to_string(),
20673    );
20674
20675    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20676        editor
20677            .snapshot(window, cx)
20678            .buffer_snapshot
20679            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20680            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20681            .collect::<Vec<_>>()
20682    });
20683    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20684    assert_eq!(
20685        actual_guides,
20686        vec![
20687            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20688            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20689            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20690        ]
20691    );
20692}
20693
20694#[gpui::test]
20695async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20696    init_test(cx, |_| {});
20697    let mut cx = EditorTestContext::new(cx).await;
20698
20699    let diff_base = r#"
20700        a
20701        b
20702        c
20703        "#
20704    .unindent();
20705
20706    cx.set_state(
20707        &r#"
20708        ˇA
20709        b
20710        C
20711        "#
20712        .unindent(),
20713    );
20714    cx.set_head_text(&diff_base);
20715    cx.update_editor(|editor, window, cx| {
20716        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20717    });
20718    executor.run_until_parked();
20719
20720    let both_hunks_expanded = r#"
20721        - a
20722        + ˇA
20723          b
20724        - c
20725        + C
20726        "#
20727    .unindent();
20728
20729    cx.assert_state_with_diff(both_hunks_expanded.clone());
20730
20731    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20732        let snapshot = editor.snapshot(window, cx);
20733        let hunks = editor
20734            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20735            .collect::<Vec<_>>();
20736        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20737        let buffer_id = hunks[0].buffer_id;
20738        hunks
20739            .into_iter()
20740            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20741            .collect::<Vec<_>>()
20742    });
20743    assert_eq!(hunk_ranges.len(), 2);
20744
20745    cx.update_editor(|editor, _, cx| {
20746        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20747    });
20748    executor.run_until_parked();
20749
20750    let second_hunk_expanded = r#"
20751          ˇA
20752          b
20753        - c
20754        + C
20755        "#
20756    .unindent();
20757
20758    cx.assert_state_with_diff(second_hunk_expanded);
20759
20760    cx.update_editor(|editor, _, cx| {
20761        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20762    });
20763    executor.run_until_parked();
20764
20765    cx.assert_state_with_diff(both_hunks_expanded.clone());
20766
20767    cx.update_editor(|editor, _, cx| {
20768        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20769    });
20770    executor.run_until_parked();
20771
20772    let first_hunk_expanded = r#"
20773        - a
20774        + ˇA
20775          b
20776          C
20777        "#
20778    .unindent();
20779
20780    cx.assert_state_with_diff(first_hunk_expanded);
20781
20782    cx.update_editor(|editor, _, cx| {
20783        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20784    });
20785    executor.run_until_parked();
20786
20787    cx.assert_state_with_diff(both_hunks_expanded);
20788
20789    cx.set_state(
20790        &r#"
20791        ˇA
20792        b
20793        "#
20794        .unindent(),
20795    );
20796    cx.run_until_parked();
20797
20798    // TODO this cursor position seems bad
20799    cx.assert_state_with_diff(
20800        r#"
20801        - ˇa
20802        + A
20803          b
20804        "#
20805        .unindent(),
20806    );
20807
20808    cx.update_editor(|editor, window, cx| {
20809        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20810    });
20811
20812    cx.assert_state_with_diff(
20813        r#"
20814            - ˇa
20815            + A
20816              b
20817            - c
20818            "#
20819        .unindent(),
20820    );
20821
20822    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20823        let snapshot = editor.snapshot(window, cx);
20824        let hunks = editor
20825            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20826            .collect::<Vec<_>>();
20827        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20828        let buffer_id = hunks[0].buffer_id;
20829        hunks
20830            .into_iter()
20831            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20832            .collect::<Vec<_>>()
20833    });
20834    assert_eq!(hunk_ranges.len(), 2);
20835
20836    cx.update_editor(|editor, _, cx| {
20837        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20838    });
20839    executor.run_until_parked();
20840
20841    cx.assert_state_with_diff(
20842        r#"
20843        - ˇa
20844        + A
20845          b
20846        "#
20847        .unindent(),
20848    );
20849}
20850
20851#[gpui::test]
20852async fn test_toggle_deletion_hunk_at_start_of_file(
20853    executor: BackgroundExecutor,
20854    cx: &mut TestAppContext,
20855) {
20856    init_test(cx, |_| {});
20857    let mut cx = EditorTestContext::new(cx).await;
20858
20859    let diff_base = r#"
20860        a
20861        b
20862        c
20863        "#
20864    .unindent();
20865
20866    cx.set_state(
20867        &r#"
20868        ˇb
20869        c
20870        "#
20871        .unindent(),
20872    );
20873    cx.set_head_text(&diff_base);
20874    cx.update_editor(|editor, window, cx| {
20875        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20876    });
20877    executor.run_until_parked();
20878
20879    let hunk_expanded = r#"
20880        - a
20881          ˇb
20882          c
20883        "#
20884    .unindent();
20885
20886    cx.assert_state_with_diff(hunk_expanded.clone());
20887
20888    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20889        let snapshot = editor.snapshot(window, cx);
20890        let hunks = editor
20891            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20892            .collect::<Vec<_>>();
20893        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20894        let buffer_id = hunks[0].buffer_id;
20895        hunks
20896            .into_iter()
20897            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20898            .collect::<Vec<_>>()
20899    });
20900    assert_eq!(hunk_ranges.len(), 1);
20901
20902    cx.update_editor(|editor, _, cx| {
20903        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20904    });
20905    executor.run_until_parked();
20906
20907    let hunk_collapsed = r#"
20908          ˇb
20909          c
20910        "#
20911    .unindent();
20912
20913    cx.assert_state_with_diff(hunk_collapsed);
20914
20915    cx.update_editor(|editor, _, cx| {
20916        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20917    });
20918    executor.run_until_parked();
20919
20920    cx.assert_state_with_diff(hunk_expanded);
20921}
20922
20923#[gpui::test]
20924async fn test_display_diff_hunks(cx: &mut TestAppContext) {
20925    init_test(cx, |_| {});
20926
20927    let fs = FakeFs::new(cx.executor());
20928    fs.insert_tree(
20929        path!("/test"),
20930        json!({
20931            ".git": {},
20932            "file-1": "ONE\n",
20933            "file-2": "TWO\n",
20934            "file-3": "THREE\n",
20935        }),
20936    )
20937    .await;
20938
20939    fs.set_head_for_repo(
20940        path!("/test/.git").as_ref(),
20941        &[
20942            ("file-1".into(), "one\n".into()),
20943            ("file-2".into(), "two\n".into()),
20944            ("file-3".into(), "three\n".into()),
20945        ],
20946        "deadbeef",
20947    );
20948
20949    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
20950    let mut buffers = vec![];
20951    for i in 1..=3 {
20952        let buffer = project
20953            .update(cx, |project, cx| {
20954                let path = format!(path!("/test/file-{}"), i);
20955                project.open_local_buffer(path, cx)
20956            })
20957            .await
20958            .unwrap();
20959        buffers.push(buffer);
20960    }
20961
20962    let multibuffer = cx.new(|cx| {
20963        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
20964        multibuffer.set_all_diff_hunks_expanded(cx);
20965        for buffer in &buffers {
20966            let snapshot = buffer.read(cx).snapshot();
20967            multibuffer.set_excerpts_for_path(
20968                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
20969                buffer.clone(),
20970                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
20971                2,
20972                cx,
20973            );
20974        }
20975        multibuffer
20976    });
20977
20978    let editor = cx.add_window(|window, cx| {
20979        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
20980    });
20981    cx.run_until_parked();
20982
20983    let snapshot = editor
20984        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20985        .unwrap();
20986    let hunks = snapshot
20987        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
20988        .map(|hunk| match hunk {
20989            DisplayDiffHunk::Unfolded {
20990                display_row_range, ..
20991            } => display_row_range,
20992            DisplayDiffHunk::Folded { .. } => unreachable!(),
20993        })
20994        .collect::<Vec<_>>();
20995    assert_eq!(
20996        hunks,
20997        [
20998            DisplayRow(2)..DisplayRow(4),
20999            DisplayRow(7)..DisplayRow(9),
21000            DisplayRow(12)..DisplayRow(14),
21001        ]
21002    );
21003}
21004
21005#[gpui::test]
21006async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21007    init_test(cx, |_| {});
21008
21009    let mut cx = EditorTestContext::new(cx).await;
21010    cx.set_head_text(indoc! { "
21011        one
21012        two
21013        three
21014        four
21015        five
21016        "
21017    });
21018    cx.set_index_text(indoc! { "
21019        one
21020        two
21021        three
21022        four
21023        five
21024        "
21025    });
21026    cx.set_state(indoc! {"
21027        one
21028        TWO
21029        ˇTHREE
21030        FOUR
21031        five
21032    "});
21033    cx.run_until_parked();
21034    cx.update_editor(|editor, window, cx| {
21035        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21036    });
21037    cx.run_until_parked();
21038    cx.assert_index_text(Some(indoc! {"
21039        one
21040        TWO
21041        THREE
21042        FOUR
21043        five
21044    "}));
21045    cx.set_state(indoc! { "
21046        one
21047        TWO
21048        ˇTHREE-HUNDRED
21049        FOUR
21050        five
21051    "});
21052    cx.run_until_parked();
21053    cx.update_editor(|editor, window, cx| {
21054        let snapshot = editor.snapshot(window, cx);
21055        let hunks = editor
21056            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
21057            .collect::<Vec<_>>();
21058        assert_eq!(hunks.len(), 1);
21059        assert_eq!(
21060            hunks[0].status(),
21061            DiffHunkStatus {
21062                kind: DiffHunkStatusKind::Modified,
21063                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21064            }
21065        );
21066
21067        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21068    });
21069    cx.run_until_parked();
21070    cx.assert_index_text(Some(indoc! {"
21071        one
21072        TWO
21073        THREE-HUNDRED
21074        FOUR
21075        five
21076    "}));
21077}
21078
21079#[gpui::test]
21080fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21081    init_test(cx, |_| {});
21082
21083    let editor = cx.add_window(|window, cx| {
21084        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21085        build_editor(buffer, window, cx)
21086    });
21087
21088    let render_args = Arc::new(Mutex::new(None));
21089    let snapshot = editor
21090        .update(cx, |editor, window, cx| {
21091            let snapshot = editor.buffer().read(cx).snapshot(cx);
21092            let range =
21093                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21094
21095            struct RenderArgs {
21096                row: MultiBufferRow,
21097                folded: bool,
21098                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21099            }
21100
21101            let crease = Crease::inline(
21102                range,
21103                FoldPlaceholder::test(),
21104                {
21105                    let toggle_callback = render_args.clone();
21106                    move |row, folded, callback, _window, _cx| {
21107                        *toggle_callback.lock() = Some(RenderArgs {
21108                            row,
21109                            folded,
21110                            callback,
21111                        });
21112                        div()
21113                    }
21114                },
21115                |_row, _folded, _window, _cx| div(),
21116            );
21117
21118            editor.insert_creases(Some(crease), cx);
21119            let snapshot = editor.snapshot(window, cx);
21120            let _div =
21121                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21122            snapshot
21123        })
21124        .unwrap();
21125
21126    let render_args = render_args.lock().take().unwrap();
21127    assert_eq!(render_args.row, MultiBufferRow(1));
21128    assert!(!render_args.folded);
21129    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21130
21131    cx.update_window(*editor, |_, window, cx| {
21132        (render_args.callback)(true, window, cx)
21133    })
21134    .unwrap();
21135    let snapshot = editor
21136        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21137        .unwrap();
21138    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21139
21140    cx.update_window(*editor, |_, window, cx| {
21141        (render_args.callback)(false, window, cx)
21142    })
21143    .unwrap();
21144    let snapshot = editor
21145        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21146        .unwrap();
21147    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21148}
21149
21150#[gpui::test]
21151async fn test_input_text(cx: &mut TestAppContext) {
21152    init_test(cx, |_| {});
21153    let mut cx = EditorTestContext::new(cx).await;
21154
21155    cx.set_state(
21156        &r#"ˇone
21157        two
21158
21159        three
21160        fourˇ
21161        five
21162
21163        siˇx"#
21164            .unindent(),
21165    );
21166
21167    cx.dispatch_action(HandleInput(String::new()));
21168    cx.assert_editor_state(
21169        &r#"ˇone
21170        two
21171
21172        three
21173        fourˇ
21174        five
21175
21176        siˇx"#
21177            .unindent(),
21178    );
21179
21180    cx.dispatch_action(HandleInput("AAAA".to_string()));
21181    cx.assert_editor_state(
21182        &r#"AAAAˇone
21183        two
21184
21185        three
21186        fourAAAAˇ
21187        five
21188
21189        siAAAAˇx"#
21190            .unindent(),
21191    );
21192}
21193
21194#[gpui::test]
21195async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21196    init_test(cx, |_| {});
21197
21198    let mut cx = EditorTestContext::new(cx).await;
21199    cx.set_state(
21200        r#"let foo = 1;
21201let foo = 2;
21202let foo = 3;
21203let fooˇ = 4;
21204let foo = 5;
21205let foo = 6;
21206let foo = 7;
21207let foo = 8;
21208let foo = 9;
21209let foo = 10;
21210let foo = 11;
21211let foo = 12;
21212let foo = 13;
21213let foo = 14;
21214let foo = 15;"#,
21215    );
21216
21217    cx.update_editor(|e, window, cx| {
21218        assert_eq!(
21219            e.next_scroll_position,
21220            NextScrollCursorCenterTopBottom::Center,
21221            "Default next scroll direction is center",
21222        );
21223
21224        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21225        assert_eq!(
21226            e.next_scroll_position,
21227            NextScrollCursorCenterTopBottom::Top,
21228            "After center, next scroll direction should be top",
21229        );
21230
21231        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21232        assert_eq!(
21233            e.next_scroll_position,
21234            NextScrollCursorCenterTopBottom::Bottom,
21235            "After top, next scroll direction should be bottom",
21236        );
21237
21238        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21239        assert_eq!(
21240            e.next_scroll_position,
21241            NextScrollCursorCenterTopBottom::Center,
21242            "After bottom, scrolling should start over",
21243        );
21244
21245        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21246        assert_eq!(
21247            e.next_scroll_position,
21248            NextScrollCursorCenterTopBottom::Top,
21249            "Scrolling continues if retriggered fast enough"
21250        );
21251    });
21252
21253    cx.executor()
21254        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21255    cx.executor().run_until_parked();
21256    cx.update_editor(|e, _, _| {
21257        assert_eq!(
21258            e.next_scroll_position,
21259            NextScrollCursorCenterTopBottom::Center,
21260            "If scrolling is not triggered fast enough, it should reset"
21261        );
21262    });
21263}
21264
21265#[gpui::test]
21266async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21267    init_test(cx, |_| {});
21268    let mut cx = EditorLspTestContext::new_rust(
21269        lsp::ServerCapabilities {
21270            definition_provider: Some(lsp::OneOf::Left(true)),
21271            references_provider: Some(lsp::OneOf::Left(true)),
21272            ..lsp::ServerCapabilities::default()
21273        },
21274        cx,
21275    )
21276    .await;
21277
21278    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21279        let go_to_definition = cx
21280            .lsp
21281            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21282                move |params, _| async move {
21283                    if empty_go_to_definition {
21284                        Ok(None)
21285                    } else {
21286                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21287                            uri: params.text_document_position_params.text_document.uri,
21288                            range: lsp::Range::new(
21289                                lsp::Position::new(4, 3),
21290                                lsp::Position::new(4, 6),
21291                            ),
21292                        })))
21293                    }
21294                },
21295            );
21296        let references = cx
21297            .lsp
21298            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21299                Ok(Some(vec![lsp::Location {
21300                    uri: params.text_document_position.text_document.uri,
21301                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21302                }]))
21303            });
21304        (go_to_definition, references)
21305    };
21306
21307    cx.set_state(
21308        &r#"fn one() {
21309            let mut a = ˇtwo();
21310        }
21311
21312        fn two() {}"#
21313            .unindent(),
21314    );
21315    set_up_lsp_handlers(false, &mut cx);
21316    let navigated = cx
21317        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21318        .await
21319        .expect("Failed to navigate to definition");
21320    assert_eq!(
21321        navigated,
21322        Navigated::Yes,
21323        "Should have navigated to definition from the GetDefinition response"
21324    );
21325    cx.assert_editor_state(
21326        &r#"fn one() {
21327            let mut a = two();
21328        }
21329
21330        fn «twoˇ»() {}"#
21331            .unindent(),
21332    );
21333
21334    let editors = cx.update_workspace(|workspace, _, cx| {
21335        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21336    });
21337    cx.update_editor(|_, _, test_editor_cx| {
21338        assert_eq!(
21339            editors.len(),
21340            1,
21341            "Initially, only one, test, editor should be open in the workspace"
21342        );
21343        assert_eq!(
21344            test_editor_cx.entity(),
21345            editors.last().expect("Asserted len is 1").clone()
21346        );
21347    });
21348
21349    set_up_lsp_handlers(true, &mut cx);
21350    let navigated = cx
21351        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21352        .await
21353        .expect("Failed to navigate to lookup references");
21354    assert_eq!(
21355        navigated,
21356        Navigated::Yes,
21357        "Should have navigated to references as a fallback after empty GoToDefinition response"
21358    );
21359    // We should not change the selections in the existing file,
21360    // if opening another milti buffer with the references
21361    cx.assert_editor_state(
21362        &r#"fn one() {
21363            let mut a = two();
21364        }
21365
21366        fn «twoˇ»() {}"#
21367            .unindent(),
21368    );
21369    let editors = cx.update_workspace(|workspace, _, cx| {
21370        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21371    });
21372    cx.update_editor(|_, _, test_editor_cx| {
21373        assert_eq!(
21374            editors.len(),
21375            2,
21376            "After falling back to references search, we open a new editor with the results"
21377        );
21378        let references_fallback_text = editors
21379            .into_iter()
21380            .find(|new_editor| *new_editor != test_editor_cx.entity())
21381            .expect("Should have one non-test editor now")
21382            .read(test_editor_cx)
21383            .text(test_editor_cx);
21384        assert_eq!(
21385            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21386            "Should use the range from the references response and not the GoToDefinition one"
21387        );
21388    });
21389}
21390
21391#[gpui::test]
21392async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21393    init_test(cx, |_| {});
21394    cx.update(|cx| {
21395        let mut editor_settings = EditorSettings::get_global(cx).clone();
21396        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21397        EditorSettings::override_global(editor_settings, cx);
21398    });
21399    let mut cx = EditorLspTestContext::new_rust(
21400        lsp::ServerCapabilities {
21401            definition_provider: Some(lsp::OneOf::Left(true)),
21402            references_provider: Some(lsp::OneOf::Left(true)),
21403            ..lsp::ServerCapabilities::default()
21404        },
21405        cx,
21406    )
21407    .await;
21408    let original_state = r#"fn one() {
21409        let mut a = ˇtwo();
21410    }
21411
21412    fn two() {}"#
21413        .unindent();
21414    cx.set_state(&original_state);
21415
21416    let mut go_to_definition = cx
21417        .lsp
21418        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21419            move |_, _| async move { Ok(None) },
21420        );
21421    let _references = cx
21422        .lsp
21423        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21424            panic!("Should not call for references with no go to definition fallback")
21425        });
21426
21427    let navigated = cx
21428        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21429        .await
21430        .expect("Failed to navigate to lookup references");
21431    go_to_definition
21432        .next()
21433        .await
21434        .expect("Should have called the go_to_definition handler");
21435
21436    assert_eq!(
21437        navigated,
21438        Navigated::No,
21439        "Should have navigated to references as a fallback after empty GoToDefinition response"
21440    );
21441    cx.assert_editor_state(&original_state);
21442    let editors = cx.update_workspace(|workspace, _, cx| {
21443        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21444    });
21445    cx.update_editor(|_, _, _| {
21446        assert_eq!(
21447            editors.len(),
21448            1,
21449            "After unsuccessful fallback, no other editor should have been opened"
21450        );
21451    });
21452}
21453
21454#[gpui::test]
21455async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21456    init_test(cx, |_| {});
21457    let mut cx = EditorLspTestContext::new_rust(
21458        lsp::ServerCapabilities {
21459            references_provider: Some(lsp::OneOf::Left(true)),
21460            ..lsp::ServerCapabilities::default()
21461        },
21462        cx,
21463    )
21464    .await;
21465
21466    cx.set_state(
21467        &r#"
21468        fn one() {
21469            let mut a = two();
21470        }
21471
21472        fn ˇtwo() {}"#
21473            .unindent(),
21474    );
21475    cx.lsp
21476        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21477            Ok(Some(vec![
21478                lsp::Location {
21479                    uri: params.text_document_position.text_document.uri.clone(),
21480                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21481                },
21482                lsp::Location {
21483                    uri: params.text_document_position.text_document.uri,
21484                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21485                },
21486            ]))
21487        });
21488    let navigated = cx
21489        .update_editor(|editor, window, cx| {
21490            editor.find_all_references(&FindAllReferences, window, cx)
21491        })
21492        .unwrap()
21493        .await
21494        .expect("Failed to navigate to references");
21495    assert_eq!(
21496        navigated,
21497        Navigated::Yes,
21498        "Should have navigated to references from the FindAllReferences response"
21499    );
21500    cx.assert_editor_state(
21501        &r#"fn one() {
21502            let mut a = two();
21503        }
21504
21505        fn ˇtwo() {}"#
21506            .unindent(),
21507    );
21508
21509    let editors = cx.update_workspace(|workspace, _, cx| {
21510        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21511    });
21512    cx.update_editor(|_, _, _| {
21513        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21514    });
21515
21516    cx.set_state(
21517        &r#"fn one() {
21518            let mut a = ˇtwo();
21519        }
21520
21521        fn two() {}"#
21522            .unindent(),
21523    );
21524    let navigated = cx
21525        .update_editor(|editor, window, cx| {
21526            editor.find_all_references(&FindAllReferences, window, cx)
21527        })
21528        .unwrap()
21529        .await
21530        .expect("Failed to navigate to references");
21531    assert_eq!(
21532        navigated,
21533        Navigated::Yes,
21534        "Should have navigated to references from the FindAllReferences response"
21535    );
21536    cx.assert_editor_state(
21537        &r#"fn one() {
21538            let mut a = ˇtwo();
21539        }
21540
21541        fn two() {}"#
21542            .unindent(),
21543    );
21544    let editors = cx.update_workspace(|workspace, _, cx| {
21545        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21546    });
21547    cx.update_editor(|_, _, _| {
21548        assert_eq!(
21549            editors.len(),
21550            2,
21551            "should have re-used the previous multibuffer"
21552        );
21553    });
21554
21555    cx.set_state(
21556        &r#"fn one() {
21557            let mut a = ˇtwo();
21558        }
21559        fn three() {}
21560        fn two() {}"#
21561            .unindent(),
21562    );
21563    cx.lsp
21564        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21565            Ok(Some(vec![
21566                lsp::Location {
21567                    uri: params.text_document_position.text_document.uri.clone(),
21568                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21569                },
21570                lsp::Location {
21571                    uri: params.text_document_position.text_document.uri,
21572                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21573                },
21574            ]))
21575        });
21576    let navigated = cx
21577        .update_editor(|editor, window, cx| {
21578            editor.find_all_references(&FindAllReferences, window, cx)
21579        })
21580        .unwrap()
21581        .await
21582        .expect("Failed to navigate to references");
21583    assert_eq!(
21584        navigated,
21585        Navigated::Yes,
21586        "Should have navigated to references from the FindAllReferences response"
21587    );
21588    cx.assert_editor_state(
21589        &r#"fn one() {
21590                let mut a = ˇtwo();
21591            }
21592            fn three() {}
21593            fn two() {}"#
21594            .unindent(),
21595    );
21596    let editors = cx.update_workspace(|workspace, _, cx| {
21597        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21598    });
21599    cx.update_editor(|_, _, _| {
21600        assert_eq!(
21601            editors.len(),
21602            3,
21603            "should have used a new multibuffer as offsets changed"
21604        );
21605    });
21606}
21607#[gpui::test]
21608async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21609    init_test(cx, |_| {});
21610
21611    let language = Arc::new(Language::new(
21612        LanguageConfig::default(),
21613        Some(tree_sitter_rust::LANGUAGE.into()),
21614    ));
21615
21616    let text = r#"
21617        #[cfg(test)]
21618        mod tests() {
21619            #[test]
21620            fn runnable_1() {
21621                let a = 1;
21622            }
21623
21624            #[test]
21625            fn runnable_2() {
21626                let a = 1;
21627                let b = 2;
21628            }
21629        }
21630    "#
21631    .unindent();
21632
21633    let fs = FakeFs::new(cx.executor());
21634    fs.insert_file("/file.rs", Default::default()).await;
21635
21636    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21637    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21638    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21639    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21640    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21641
21642    let editor = cx.new_window_entity(|window, cx| {
21643        Editor::new(
21644            EditorMode::full(),
21645            multi_buffer,
21646            Some(project.clone()),
21647            window,
21648            cx,
21649        )
21650    });
21651
21652    editor.update_in(cx, |editor, window, cx| {
21653        let snapshot = editor.buffer().read(cx).snapshot(cx);
21654        editor.tasks.insert(
21655            (buffer.read(cx).remote_id(), 3),
21656            RunnableTasks {
21657                templates: vec![],
21658                offset: snapshot.anchor_before(43),
21659                column: 0,
21660                extra_variables: HashMap::default(),
21661                context_range: BufferOffset(43)..BufferOffset(85),
21662            },
21663        );
21664        editor.tasks.insert(
21665            (buffer.read(cx).remote_id(), 8),
21666            RunnableTasks {
21667                templates: vec![],
21668                offset: snapshot.anchor_before(86),
21669                column: 0,
21670                extra_variables: HashMap::default(),
21671                context_range: BufferOffset(86)..BufferOffset(191),
21672            },
21673        );
21674
21675        // Test finding task when cursor is inside function body
21676        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21677            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21678        });
21679        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21680        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21681
21682        // Test finding task when cursor is on function name
21683        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21684            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21685        });
21686        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21687        assert_eq!(row, 8, "Should find task when cursor is on function name");
21688    });
21689}
21690
21691#[gpui::test]
21692async fn test_folding_buffers(cx: &mut TestAppContext) {
21693    init_test(cx, |_| {});
21694
21695    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21696    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21697    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21698
21699    let fs = FakeFs::new(cx.executor());
21700    fs.insert_tree(
21701        path!("/a"),
21702        json!({
21703            "first.rs": sample_text_1,
21704            "second.rs": sample_text_2,
21705            "third.rs": sample_text_3,
21706        }),
21707    )
21708    .await;
21709    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21710    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21711    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21712    let worktree = project.update(cx, |project, cx| {
21713        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21714        assert_eq!(worktrees.len(), 1);
21715        worktrees.pop().unwrap()
21716    });
21717    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21718
21719    let buffer_1 = project
21720        .update(cx, |project, cx| {
21721            project.open_buffer((worktree_id, "first.rs"), cx)
21722        })
21723        .await
21724        .unwrap();
21725    let buffer_2 = project
21726        .update(cx, |project, cx| {
21727            project.open_buffer((worktree_id, "second.rs"), cx)
21728        })
21729        .await
21730        .unwrap();
21731    let buffer_3 = project
21732        .update(cx, |project, cx| {
21733            project.open_buffer((worktree_id, "third.rs"), cx)
21734        })
21735        .await
21736        .unwrap();
21737
21738    let multi_buffer = cx.new(|cx| {
21739        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21740        multi_buffer.push_excerpts(
21741            buffer_1.clone(),
21742            [
21743                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21744                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21745                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21746            ],
21747            cx,
21748        );
21749        multi_buffer.push_excerpts(
21750            buffer_2.clone(),
21751            [
21752                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21753                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21754                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21755            ],
21756            cx,
21757        );
21758        multi_buffer.push_excerpts(
21759            buffer_3.clone(),
21760            [
21761                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21762                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21763                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21764            ],
21765            cx,
21766        );
21767        multi_buffer
21768    });
21769    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21770        Editor::new(
21771            EditorMode::full(),
21772            multi_buffer.clone(),
21773            Some(project.clone()),
21774            window,
21775            cx,
21776        )
21777    });
21778
21779    assert_eq!(
21780        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21781        "\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",
21782    );
21783
21784    multi_buffer_editor.update(cx, |editor, cx| {
21785        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21786    });
21787    assert_eq!(
21788        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21789        "\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",
21790        "After folding the first buffer, its text should not be displayed"
21791    );
21792
21793    multi_buffer_editor.update(cx, |editor, cx| {
21794        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21795    });
21796    assert_eq!(
21797        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21798        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21799        "After folding the second buffer, its text should not be displayed"
21800    );
21801
21802    multi_buffer_editor.update(cx, |editor, cx| {
21803        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21804    });
21805    assert_eq!(
21806        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21807        "\n\n\n\n\n",
21808        "After folding the third buffer, its text should not be displayed"
21809    );
21810
21811    // Emulate selection inside the fold logic, that should work
21812    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21813        editor
21814            .snapshot(window, cx)
21815            .next_line_boundary(Point::new(0, 4));
21816    });
21817
21818    multi_buffer_editor.update(cx, |editor, cx| {
21819        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21820    });
21821    assert_eq!(
21822        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21823        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21824        "After unfolding the second buffer, its text should be displayed"
21825    );
21826
21827    // Typing inside of buffer 1 causes that buffer to be unfolded.
21828    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21829        assert_eq!(
21830            multi_buffer
21831                .read(cx)
21832                .snapshot(cx)
21833                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21834                .collect::<String>(),
21835            "bbbb"
21836        );
21837        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21838            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
21839        });
21840        editor.handle_input("B", window, cx);
21841    });
21842
21843    assert_eq!(
21844        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21845        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21846        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
21847    );
21848
21849    multi_buffer_editor.update(cx, |editor, cx| {
21850        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21851    });
21852    assert_eq!(
21853        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21854        "\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",
21855        "After unfolding the all buffers, all original text should be displayed"
21856    );
21857}
21858
21859#[gpui::test]
21860async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
21861    init_test(cx, |_| {});
21862
21863    let sample_text_1 = "1111\n2222\n3333".to_string();
21864    let sample_text_2 = "4444\n5555\n6666".to_string();
21865    let sample_text_3 = "7777\n8888\n9999".to_string();
21866
21867    let fs = FakeFs::new(cx.executor());
21868    fs.insert_tree(
21869        path!("/a"),
21870        json!({
21871            "first.rs": sample_text_1,
21872            "second.rs": sample_text_2,
21873            "third.rs": sample_text_3,
21874        }),
21875    )
21876    .await;
21877    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21878    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21879    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21880    let worktree = project.update(cx, |project, cx| {
21881        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21882        assert_eq!(worktrees.len(), 1);
21883        worktrees.pop().unwrap()
21884    });
21885    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21886
21887    let buffer_1 = project
21888        .update(cx, |project, cx| {
21889            project.open_buffer((worktree_id, "first.rs"), cx)
21890        })
21891        .await
21892        .unwrap();
21893    let buffer_2 = project
21894        .update(cx, |project, cx| {
21895            project.open_buffer((worktree_id, "second.rs"), cx)
21896        })
21897        .await
21898        .unwrap();
21899    let buffer_3 = project
21900        .update(cx, |project, cx| {
21901            project.open_buffer((worktree_id, "third.rs"), cx)
21902        })
21903        .await
21904        .unwrap();
21905
21906    let multi_buffer = cx.new(|cx| {
21907        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21908        multi_buffer.push_excerpts(
21909            buffer_1.clone(),
21910            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21911            cx,
21912        );
21913        multi_buffer.push_excerpts(
21914            buffer_2.clone(),
21915            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21916            cx,
21917        );
21918        multi_buffer.push_excerpts(
21919            buffer_3.clone(),
21920            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21921            cx,
21922        );
21923        multi_buffer
21924    });
21925
21926    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21927        Editor::new(
21928            EditorMode::full(),
21929            multi_buffer,
21930            Some(project.clone()),
21931            window,
21932            cx,
21933        )
21934    });
21935
21936    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
21937    assert_eq!(
21938        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21939        full_text,
21940    );
21941
21942    multi_buffer_editor.update(cx, |editor, cx| {
21943        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21944    });
21945    assert_eq!(
21946        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21947        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
21948        "After folding the first buffer, its text should not be displayed"
21949    );
21950
21951    multi_buffer_editor.update(cx, |editor, cx| {
21952        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21953    });
21954
21955    assert_eq!(
21956        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21957        "\n\n\n\n\n\n7777\n8888\n9999",
21958        "After folding the second buffer, its text should not be displayed"
21959    );
21960
21961    multi_buffer_editor.update(cx, |editor, cx| {
21962        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21963    });
21964    assert_eq!(
21965        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21966        "\n\n\n\n\n",
21967        "After folding the third buffer, its text should not be displayed"
21968    );
21969
21970    multi_buffer_editor.update(cx, |editor, cx| {
21971        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21972    });
21973    assert_eq!(
21974        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21975        "\n\n\n\n4444\n5555\n6666\n\n",
21976        "After unfolding the second buffer, its text should be displayed"
21977    );
21978
21979    multi_buffer_editor.update(cx, |editor, cx| {
21980        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
21981    });
21982    assert_eq!(
21983        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21984        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
21985        "After unfolding the first buffer, its text should be displayed"
21986    );
21987
21988    multi_buffer_editor.update(cx, |editor, cx| {
21989        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21990    });
21991    assert_eq!(
21992        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21993        full_text,
21994        "After unfolding all buffers, all original text should be displayed"
21995    );
21996}
21997
21998#[gpui::test]
21999async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22000    init_test(cx, |_| {});
22001
22002    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22003
22004    let fs = FakeFs::new(cx.executor());
22005    fs.insert_tree(
22006        path!("/a"),
22007        json!({
22008            "main.rs": sample_text,
22009        }),
22010    )
22011    .await;
22012    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22013    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22014    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22015    let worktree = project.update(cx, |project, cx| {
22016        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22017        assert_eq!(worktrees.len(), 1);
22018        worktrees.pop().unwrap()
22019    });
22020    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22021
22022    let buffer_1 = project
22023        .update(cx, |project, cx| {
22024            project.open_buffer((worktree_id, "main.rs"), cx)
22025        })
22026        .await
22027        .unwrap();
22028
22029    let multi_buffer = cx.new(|cx| {
22030        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22031        multi_buffer.push_excerpts(
22032            buffer_1.clone(),
22033            [ExcerptRange::new(
22034                Point::new(0, 0)
22035                    ..Point::new(
22036                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22037                        0,
22038                    ),
22039            )],
22040            cx,
22041        );
22042        multi_buffer
22043    });
22044    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22045        Editor::new(
22046            EditorMode::full(),
22047            multi_buffer,
22048            Some(project.clone()),
22049            window,
22050            cx,
22051        )
22052    });
22053
22054    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22055    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22056        enum TestHighlight {}
22057        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22058        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22059        editor.highlight_text::<TestHighlight>(
22060            vec![highlight_range.clone()],
22061            HighlightStyle::color(Hsla::green()),
22062            cx,
22063        );
22064        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22065            s.select_ranges(Some(highlight_range))
22066        });
22067    });
22068
22069    let full_text = format!("\n\n{sample_text}");
22070    assert_eq!(
22071        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22072        full_text,
22073    );
22074}
22075
22076#[gpui::test]
22077async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22078    init_test(cx, |_| {});
22079    cx.update(|cx| {
22080        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22081            "keymaps/default-linux.json",
22082            cx,
22083        )
22084        .unwrap();
22085        cx.bind_keys(default_key_bindings);
22086    });
22087
22088    let (editor, cx) = cx.add_window_view(|window, cx| {
22089        let multi_buffer = MultiBuffer::build_multi(
22090            [
22091                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22092                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22093                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22094                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22095            ],
22096            cx,
22097        );
22098        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22099
22100        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22101        // fold all but the second buffer, so that we test navigating between two
22102        // adjacent folded buffers, as well as folded buffers at the start and
22103        // end the multibuffer
22104        editor.fold_buffer(buffer_ids[0], cx);
22105        editor.fold_buffer(buffer_ids[2], cx);
22106        editor.fold_buffer(buffer_ids[3], cx);
22107
22108        editor
22109    });
22110    cx.simulate_resize(size(px(1000.), px(1000.)));
22111
22112    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22113    cx.assert_excerpts_with_selections(indoc! {"
22114        [EXCERPT]
22115        ˇ[FOLDED]
22116        [EXCERPT]
22117        a1
22118        b1
22119        [EXCERPT]
22120        [FOLDED]
22121        [EXCERPT]
22122        [FOLDED]
22123        "
22124    });
22125    cx.simulate_keystroke("down");
22126    cx.assert_excerpts_with_selections(indoc! {"
22127        [EXCERPT]
22128        [FOLDED]
22129        [EXCERPT]
22130        ˇa1
22131        b1
22132        [EXCERPT]
22133        [FOLDED]
22134        [EXCERPT]
22135        [FOLDED]
22136        "
22137    });
22138    cx.simulate_keystroke("down");
22139    cx.assert_excerpts_with_selections(indoc! {"
22140        [EXCERPT]
22141        [FOLDED]
22142        [EXCERPT]
22143        a1
22144        ˇb1
22145        [EXCERPT]
22146        [FOLDED]
22147        [EXCERPT]
22148        [FOLDED]
22149        "
22150    });
22151    cx.simulate_keystroke("down");
22152    cx.assert_excerpts_with_selections(indoc! {"
22153        [EXCERPT]
22154        [FOLDED]
22155        [EXCERPT]
22156        a1
22157        b1
22158        ˇ[EXCERPT]
22159        [FOLDED]
22160        [EXCERPT]
22161        [FOLDED]
22162        "
22163    });
22164    cx.simulate_keystroke("down");
22165    cx.assert_excerpts_with_selections(indoc! {"
22166        [EXCERPT]
22167        [FOLDED]
22168        [EXCERPT]
22169        a1
22170        b1
22171        [EXCERPT]
22172        ˇ[FOLDED]
22173        [EXCERPT]
22174        [FOLDED]
22175        "
22176    });
22177    for _ in 0..5 {
22178        cx.simulate_keystroke("down");
22179        cx.assert_excerpts_with_selections(indoc! {"
22180            [EXCERPT]
22181            [FOLDED]
22182            [EXCERPT]
22183            a1
22184            b1
22185            [EXCERPT]
22186            [FOLDED]
22187            [EXCERPT]
22188            ˇ[FOLDED]
22189            "
22190        });
22191    }
22192
22193    cx.simulate_keystroke("up");
22194    cx.assert_excerpts_with_selections(indoc! {"
22195        [EXCERPT]
22196        [FOLDED]
22197        [EXCERPT]
22198        a1
22199        b1
22200        [EXCERPT]
22201        ˇ[FOLDED]
22202        [EXCERPT]
22203        [FOLDED]
22204        "
22205    });
22206    cx.simulate_keystroke("up");
22207    cx.assert_excerpts_with_selections(indoc! {"
22208        [EXCERPT]
22209        [FOLDED]
22210        [EXCERPT]
22211        a1
22212        b1
22213        ˇ[EXCERPT]
22214        [FOLDED]
22215        [EXCERPT]
22216        [FOLDED]
22217        "
22218    });
22219    cx.simulate_keystroke("up");
22220    cx.assert_excerpts_with_selections(indoc! {"
22221        [EXCERPT]
22222        [FOLDED]
22223        [EXCERPT]
22224        a1
22225        ˇb1
22226        [EXCERPT]
22227        [FOLDED]
22228        [EXCERPT]
22229        [FOLDED]
22230        "
22231    });
22232    cx.simulate_keystroke("up");
22233    cx.assert_excerpts_with_selections(indoc! {"
22234        [EXCERPT]
22235        [FOLDED]
22236        [EXCERPT]
22237        ˇa1
22238        b1
22239        [EXCERPT]
22240        [FOLDED]
22241        [EXCERPT]
22242        [FOLDED]
22243        "
22244    });
22245    for _ in 0..5 {
22246        cx.simulate_keystroke("up");
22247        cx.assert_excerpts_with_selections(indoc! {"
22248            [EXCERPT]
22249            ˇ[FOLDED]
22250            [EXCERPT]
22251            a1
22252            b1
22253            [EXCERPT]
22254            [FOLDED]
22255            [EXCERPT]
22256            [FOLDED]
22257            "
22258        });
22259    }
22260}
22261
22262#[gpui::test]
22263async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22264    init_test(cx, |_| {});
22265
22266    // Simple insertion
22267    assert_highlighted_edits(
22268        "Hello, world!",
22269        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22270        true,
22271        cx,
22272        |highlighted_edits, cx| {
22273            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22274            assert_eq!(highlighted_edits.highlights.len(), 1);
22275            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22276            assert_eq!(
22277                highlighted_edits.highlights[0].1.background_color,
22278                Some(cx.theme().status().created_background)
22279            );
22280        },
22281    )
22282    .await;
22283
22284    // Replacement
22285    assert_highlighted_edits(
22286        "This is a test.",
22287        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22288        false,
22289        cx,
22290        |highlighted_edits, cx| {
22291            assert_eq!(highlighted_edits.text, "That is a test.");
22292            assert_eq!(highlighted_edits.highlights.len(), 1);
22293            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22294            assert_eq!(
22295                highlighted_edits.highlights[0].1.background_color,
22296                Some(cx.theme().status().created_background)
22297            );
22298        },
22299    )
22300    .await;
22301
22302    // Multiple edits
22303    assert_highlighted_edits(
22304        "Hello, world!",
22305        vec![
22306            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22307            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22308        ],
22309        false,
22310        cx,
22311        |highlighted_edits, cx| {
22312            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22313            assert_eq!(highlighted_edits.highlights.len(), 2);
22314            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22315            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22316            assert_eq!(
22317                highlighted_edits.highlights[0].1.background_color,
22318                Some(cx.theme().status().created_background)
22319            );
22320            assert_eq!(
22321                highlighted_edits.highlights[1].1.background_color,
22322                Some(cx.theme().status().created_background)
22323            );
22324        },
22325    )
22326    .await;
22327
22328    // Multiple lines with edits
22329    assert_highlighted_edits(
22330        "First line\nSecond line\nThird line\nFourth line",
22331        vec![
22332            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22333            (
22334                Point::new(2, 0)..Point::new(2, 10),
22335                "New third line".to_string(),
22336            ),
22337            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22338        ],
22339        false,
22340        cx,
22341        |highlighted_edits, cx| {
22342            assert_eq!(
22343                highlighted_edits.text,
22344                "Second modified\nNew third line\nFourth updated line"
22345            );
22346            assert_eq!(highlighted_edits.highlights.len(), 3);
22347            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22348            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22349            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22350            for highlight in &highlighted_edits.highlights {
22351                assert_eq!(
22352                    highlight.1.background_color,
22353                    Some(cx.theme().status().created_background)
22354                );
22355            }
22356        },
22357    )
22358    .await;
22359}
22360
22361#[gpui::test]
22362async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22363    init_test(cx, |_| {});
22364
22365    // Deletion
22366    assert_highlighted_edits(
22367        "Hello, world!",
22368        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22369        true,
22370        cx,
22371        |highlighted_edits, cx| {
22372            assert_eq!(highlighted_edits.text, "Hello, world!");
22373            assert_eq!(highlighted_edits.highlights.len(), 1);
22374            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22375            assert_eq!(
22376                highlighted_edits.highlights[0].1.background_color,
22377                Some(cx.theme().status().deleted_background)
22378            );
22379        },
22380    )
22381    .await;
22382
22383    // Insertion
22384    assert_highlighted_edits(
22385        "Hello, world!",
22386        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22387        true,
22388        cx,
22389        |highlighted_edits, cx| {
22390            assert_eq!(highlighted_edits.highlights.len(), 1);
22391            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22392            assert_eq!(
22393                highlighted_edits.highlights[0].1.background_color,
22394                Some(cx.theme().status().created_background)
22395            );
22396        },
22397    )
22398    .await;
22399}
22400
22401async fn assert_highlighted_edits(
22402    text: &str,
22403    edits: Vec<(Range<Point>, String)>,
22404    include_deletions: bool,
22405    cx: &mut TestAppContext,
22406    assertion_fn: impl Fn(HighlightedText, &App),
22407) {
22408    let window = cx.add_window(|window, cx| {
22409        let buffer = MultiBuffer::build_simple(text, cx);
22410        Editor::new(EditorMode::full(), buffer, None, window, cx)
22411    });
22412    let cx = &mut VisualTestContext::from_window(*window, cx);
22413
22414    let (buffer, snapshot) = window
22415        .update(cx, |editor, _window, cx| {
22416            (
22417                editor.buffer().clone(),
22418                editor.buffer().read(cx).snapshot(cx),
22419            )
22420        })
22421        .unwrap();
22422
22423    let edits = edits
22424        .into_iter()
22425        .map(|(range, edit)| {
22426            (
22427                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22428                edit,
22429            )
22430        })
22431        .collect::<Vec<_>>();
22432
22433    let text_anchor_edits = edits
22434        .clone()
22435        .into_iter()
22436        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22437        .collect::<Vec<_>>();
22438
22439    let edit_preview = window
22440        .update(cx, |_, _window, cx| {
22441            buffer
22442                .read(cx)
22443                .as_singleton()
22444                .unwrap()
22445                .read(cx)
22446                .preview_edits(text_anchor_edits.into(), cx)
22447        })
22448        .unwrap()
22449        .await;
22450
22451    cx.update(|_window, cx| {
22452        let highlighted_edits = edit_prediction_edit_text(
22453            snapshot.as_singleton().unwrap().2,
22454            &edits,
22455            &edit_preview,
22456            include_deletions,
22457            cx,
22458        );
22459        assertion_fn(highlighted_edits, cx)
22460    });
22461}
22462
22463#[track_caller]
22464fn assert_breakpoint(
22465    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22466    path: &Arc<Path>,
22467    expected: Vec<(u32, Breakpoint)>,
22468) {
22469    if expected.is_empty() {
22470        assert!(!breakpoints.contains_key(path), "{}", path.display());
22471    } else {
22472        let mut breakpoint = breakpoints
22473            .get(path)
22474            .unwrap()
22475            .iter()
22476            .map(|breakpoint| {
22477                (
22478                    breakpoint.row,
22479                    Breakpoint {
22480                        message: breakpoint.message.clone(),
22481                        state: breakpoint.state,
22482                        condition: breakpoint.condition.clone(),
22483                        hit_condition: breakpoint.hit_condition.clone(),
22484                    },
22485                )
22486            })
22487            .collect::<Vec<_>>();
22488
22489        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22490
22491        assert_eq!(expected, breakpoint);
22492    }
22493}
22494
22495fn add_log_breakpoint_at_cursor(
22496    editor: &mut Editor,
22497    log_message: &str,
22498    window: &mut Window,
22499    cx: &mut Context<Editor>,
22500) {
22501    let (anchor, bp) = editor
22502        .breakpoints_at_cursors(window, cx)
22503        .first()
22504        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22505        .unwrap_or_else(|| {
22506            let cursor_position: Point = editor.selections.newest(cx).head();
22507
22508            let breakpoint_position = editor
22509                .snapshot(window, cx)
22510                .display_snapshot
22511                .buffer_snapshot
22512                .anchor_before(Point::new(cursor_position.row, 0));
22513
22514            (breakpoint_position, Breakpoint::new_log(log_message))
22515        });
22516
22517    editor.edit_breakpoint_at_anchor(
22518        anchor,
22519        bp,
22520        BreakpointEditAction::EditLogMessage(log_message.into()),
22521        cx,
22522    );
22523}
22524
22525#[gpui::test]
22526async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22527    init_test(cx, |_| {});
22528
22529    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22530    let fs = FakeFs::new(cx.executor());
22531    fs.insert_tree(
22532        path!("/a"),
22533        json!({
22534            "main.rs": sample_text,
22535        }),
22536    )
22537    .await;
22538    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22539    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22540    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22541
22542    let fs = FakeFs::new(cx.executor());
22543    fs.insert_tree(
22544        path!("/a"),
22545        json!({
22546            "main.rs": sample_text,
22547        }),
22548    )
22549    .await;
22550    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22551    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22552    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22553    let worktree_id = workspace
22554        .update(cx, |workspace, _window, cx| {
22555            workspace.project().update(cx, |project, cx| {
22556                project.worktrees(cx).next().unwrap().read(cx).id()
22557            })
22558        })
22559        .unwrap();
22560
22561    let buffer = project
22562        .update(cx, |project, cx| {
22563            project.open_buffer((worktree_id, "main.rs"), cx)
22564        })
22565        .await
22566        .unwrap();
22567
22568    let (editor, cx) = cx.add_window_view(|window, cx| {
22569        Editor::new(
22570            EditorMode::full(),
22571            MultiBuffer::build_from_buffer(buffer, cx),
22572            Some(project.clone()),
22573            window,
22574            cx,
22575        )
22576    });
22577
22578    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22579    let abs_path = project.read_with(cx, |project, cx| {
22580        project
22581            .absolute_path(&project_path, cx)
22582            .map(Arc::from)
22583            .unwrap()
22584    });
22585
22586    // assert we can add breakpoint on the first line
22587    editor.update_in(cx, |editor, window, cx| {
22588        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22589        editor.move_to_end(&MoveToEnd, window, cx);
22590        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22591    });
22592
22593    let breakpoints = editor.update(cx, |editor, cx| {
22594        editor
22595            .breakpoint_store()
22596            .as_ref()
22597            .unwrap()
22598            .read(cx)
22599            .all_source_breakpoints(cx)
22600    });
22601
22602    assert_eq!(1, breakpoints.len());
22603    assert_breakpoint(
22604        &breakpoints,
22605        &abs_path,
22606        vec![
22607            (0, Breakpoint::new_standard()),
22608            (3, Breakpoint::new_standard()),
22609        ],
22610    );
22611
22612    editor.update_in(cx, |editor, window, cx| {
22613        editor.move_to_beginning(&MoveToBeginning, window, cx);
22614        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22615    });
22616
22617    let breakpoints = editor.update(cx, |editor, cx| {
22618        editor
22619            .breakpoint_store()
22620            .as_ref()
22621            .unwrap()
22622            .read(cx)
22623            .all_source_breakpoints(cx)
22624    });
22625
22626    assert_eq!(1, breakpoints.len());
22627    assert_breakpoint(
22628        &breakpoints,
22629        &abs_path,
22630        vec![(3, Breakpoint::new_standard())],
22631    );
22632
22633    editor.update_in(cx, |editor, window, cx| {
22634        editor.move_to_end(&MoveToEnd, window, cx);
22635        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22636    });
22637
22638    let breakpoints = editor.update(cx, |editor, cx| {
22639        editor
22640            .breakpoint_store()
22641            .as_ref()
22642            .unwrap()
22643            .read(cx)
22644            .all_source_breakpoints(cx)
22645    });
22646
22647    assert_eq!(0, breakpoints.len());
22648    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22649}
22650
22651#[gpui::test]
22652async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22653    init_test(cx, |_| {});
22654
22655    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22656
22657    let fs = FakeFs::new(cx.executor());
22658    fs.insert_tree(
22659        path!("/a"),
22660        json!({
22661            "main.rs": sample_text,
22662        }),
22663    )
22664    .await;
22665    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22666    let (workspace, cx) =
22667        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22668
22669    let worktree_id = workspace.update(cx, |workspace, cx| {
22670        workspace.project().update(cx, |project, cx| {
22671            project.worktrees(cx).next().unwrap().read(cx).id()
22672        })
22673    });
22674
22675    let buffer = project
22676        .update(cx, |project, cx| {
22677            project.open_buffer((worktree_id, "main.rs"), cx)
22678        })
22679        .await
22680        .unwrap();
22681
22682    let (editor, cx) = cx.add_window_view(|window, cx| {
22683        Editor::new(
22684            EditorMode::full(),
22685            MultiBuffer::build_from_buffer(buffer, cx),
22686            Some(project.clone()),
22687            window,
22688            cx,
22689        )
22690    });
22691
22692    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22693    let abs_path = project.read_with(cx, |project, cx| {
22694        project
22695            .absolute_path(&project_path, cx)
22696            .map(Arc::from)
22697            .unwrap()
22698    });
22699
22700    editor.update_in(cx, |editor, window, cx| {
22701        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22702    });
22703
22704    let breakpoints = editor.update(cx, |editor, cx| {
22705        editor
22706            .breakpoint_store()
22707            .as_ref()
22708            .unwrap()
22709            .read(cx)
22710            .all_source_breakpoints(cx)
22711    });
22712
22713    assert_breakpoint(
22714        &breakpoints,
22715        &abs_path,
22716        vec![(0, Breakpoint::new_log("hello world"))],
22717    );
22718
22719    // Removing a log message from a log breakpoint should remove it
22720    editor.update_in(cx, |editor, window, cx| {
22721        add_log_breakpoint_at_cursor(editor, "", window, cx);
22722    });
22723
22724    let breakpoints = editor.update(cx, |editor, cx| {
22725        editor
22726            .breakpoint_store()
22727            .as_ref()
22728            .unwrap()
22729            .read(cx)
22730            .all_source_breakpoints(cx)
22731    });
22732
22733    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22734
22735    editor.update_in(cx, |editor, window, cx| {
22736        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22737        editor.move_to_end(&MoveToEnd, window, cx);
22738        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22739        // Not adding a log message to a standard breakpoint shouldn't remove it
22740        add_log_breakpoint_at_cursor(editor, "", window, cx);
22741    });
22742
22743    let breakpoints = editor.update(cx, |editor, cx| {
22744        editor
22745            .breakpoint_store()
22746            .as_ref()
22747            .unwrap()
22748            .read(cx)
22749            .all_source_breakpoints(cx)
22750    });
22751
22752    assert_breakpoint(
22753        &breakpoints,
22754        &abs_path,
22755        vec![
22756            (0, Breakpoint::new_standard()),
22757            (3, Breakpoint::new_standard()),
22758        ],
22759    );
22760
22761    editor.update_in(cx, |editor, window, cx| {
22762        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22763    });
22764
22765    let breakpoints = editor.update(cx, |editor, cx| {
22766        editor
22767            .breakpoint_store()
22768            .as_ref()
22769            .unwrap()
22770            .read(cx)
22771            .all_source_breakpoints(cx)
22772    });
22773
22774    assert_breakpoint(
22775        &breakpoints,
22776        &abs_path,
22777        vec![
22778            (0, Breakpoint::new_standard()),
22779            (3, Breakpoint::new_log("hello world")),
22780        ],
22781    );
22782
22783    editor.update_in(cx, |editor, window, cx| {
22784        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22785    });
22786
22787    let breakpoints = editor.update(cx, |editor, cx| {
22788        editor
22789            .breakpoint_store()
22790            .as_ref()
22791            .unwrap()
22792            .read(cx)
22793            .all_source_breakpoints(cx)
22794    });
22795
22796    assert_breakpoint(
22797        &breakpoints,
22798        &abs_path,
22799        vec![
22800            (0, Breakpoint::new_standard()),
22801            (3, Breakpoint::new_log("hello Earth!!")),
22802        ],
22803    );
22804}
22805
22806/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22807/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22808/// or when breakpoints were placed out of order. This tests for a regression too
22809#[gpui::test]
22810async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22811    init_test(cx, |_| {});
22812
22813    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22814    let fs = FakeFs::new(cx.executor());
22815    fs.insert_tree(
22816        path!("/a"),
22817        json!({
22818            "main.rs": sample_text,
22819        }),
22820    )
22821    .await;
22822    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22823    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22824    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22825
22826    let fs = FakeFs::new(cx.executor());
22827    fs.insert_tree(
22828        path!("/a"),
22829        json!({
22830            "main.rs": sample_text,
22831        }),
22832    )
22833    .await;
22834    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22835    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22836    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22837    let worktree_id = workspace
22838        .update(cx, |workspace, _window, cx| {
22839            workspace.project().update(cx, |project, cx| {
22840                project.worktrees(cx).next().unwrap().read(cx).id()
22841            })
22842        })
22843        .unwrap();
22844
22845    let buffer = project
22846        .update(cx, |project, cx| {
22847            project.open_buffer((worktree_id, "main.rs"), cx)
22848        })
22849        .await
22850        .unwrap();
22851
22852    let (editor, cx) = cx.add_window_view(|window, cx| {
22853        Editor::new(
22854            EditorMode::full(),
22855            MultiBuffer::build_from_buffer(buffer, cx),
22856            Some(project.clone()),
22857            window,
22858            cx,
22859        )
22860    });
22861
22862    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22863    let abs_path = project.read_with(cx, |project, cx| {
22864        project
22865            .absolute_path(&project_path, cx)
22866            .map(Arc::from)
22867            .unwrap()
22868    });
22869
22870    // assert we can add breakpoint on the first line
22871    editor.update_in(cx, |editor, window, cx| {
22872        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22873        editor.move_to_end(&MoveToEnd, window, cx);
22874        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22875        editor.move_up(&MoveUp, window, cx);
22876        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22877    });
22878
22879    let breakpoints = editor.update(cx, |editor, cx| {
22880        editor
22881            .breakpoint_store()
22882            .as_ref()
22883            .unwrap()
22884            .read(cx)
22885            .all_source_breakpoints(cx)
22886    });
22887
22888    assert_eq!(1, breakpoints.len());
22889    assert_breakpoint(
22890        &breakpoints,
22891        &abs_path,
22892        vec![
22893            (0, Breakpoint::new_standard()),
22894            (2, Breakpoint::new_standard()),
22895            (3, Breakpoint::new_standard()),
22896        ],
22897    );
22898
22899    editor.update_in(cx, |editor, window, cx| {
22900        editor.move_to_beginning(&MoveToBeginning, window, cx);
22901        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22902        editor.move_to_end(&MoveToEnd, window, cx);
22903        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22904        // Disabling a breakpoint that doesn't exist should do nothing
22905        editor.move_up(&MoveUp, window, cx);
22906        editor.move_up(&MoveUp, window, cx);
22907        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22908    });
22909
22910    let breakpoints = editor.update(cx, |editor, cx| {
22911        editor
22912            .breakpoint_store()
22913            .as_ref()
22914            .unwrap()
22915            .read(cx)
22916            .all_source_breakpoints(cx)
22917    });
22918
22919    let disable_breakpoint = {
22920        let mut bp = Breakpoint::new_standard();
22921        bp.state = BreakpointState::Disabled;
22922        bp
22923    };
22924
22925    assert_eq!(1, breakpoints.len());
22926    assert_breakpoint(
22927        &breakpoints,
22928        &abs_path,
22929        vec![
22930            (0, disable_breakpoint.clone()),
22931            (2, Breakpoint::new_standard()),
22932            (3, disable_breakpoint.clone()),
22933        ],
22934    );
22935
22936    editor.update_in(cx, |editor, window, cx| {
22937        editor.move_to_beginning(&MoveToBeginning, window, cx);
22938        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22939        editor.move_to_end(&MoveToEnd, window, cx);
22940        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22941        editor.move_up(&MoveUp, window, cx);
22942        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22943    });
22944
22945    let breakpoints = editor.update(cx, |editor, cx| {
22946        editor
22947            .breakpoint_store()
22948            .as_ref()
22949            .unwrap()
22950            .read(cx)
22951            .all_source_breakpoints(cx)
22952    });
22953
22954    assert_eq!(1, breakpoints.len());
22955    assert_breakpoint(
22956        &breakpoints,
22957        &abs_path,
22958        vec![
22959            (0, Breakpoint::new_standard()),
22960            (2, disable_breakpoint),
22961            (3, Breakpoint::new_standard()),
22962        ],
22963    );
22964}
22965
22966#[gpui::test]
22967async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
22968    init_test(cx, |_| {});
22969    let capabilities = lsp::ServerCapabilities {
22970        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
22971            prepare_provider: Some(true),
22972            work_done_progress_options: Default::default(),
22973        })),
22974        ..Default::default()
22975    };
22976    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22977
22978    cx.set_state(indoc! {"
22979        struct Fˇoo {}
22980    "});
22981
22982    cx.update_editor(|editor, _, cx| {
22983        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22984        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22985        editor.highlight_background::<DocumentHighlightRead>(
22986            &[highlight_range],
22987            |theme| theme.colors().editor_document_highlight_read_background,
22988            cx,
22989        );
22990    });
22991
22992    let mut prepare_rename_handler = cx
22993        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
22994            move |_, _, _| async move {
22995                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
22996                    start: lsp::Position {
22997                        line: 0,
22998                        character: 7,
22999                    },
23000                    end: lsp::Position {
23001                        line: 0,
23002                        character: 10,
23003                    },
23004                })))
23005            },
23006        );
23007    let prepare_rename_task = cx
23008        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23009        .expect("Prepare rename was not started");
23010    prepare_rename_handler.next().await.unwrap();
23011    prepare_rename_task.await.expect("Prepare rename failed");
23012
23013    let mut rename_handler =
23014        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23015            let edit = lsp::TextEdit {
23016                range: lsp::Range {
23017                    start: lsp::Position {
23018                        line: 0,
23019                        character: 7,
23020                    },
23021                    end: lsp::Position {
23022                        line: 0,
23023                        character: 10,
23024                    },
23025                },
23026                new_text: "FooRenamed".to_string(),
23027            };
23028            Ok(Some(lsp::WorkspaceEdit::new(
23029                // Specify the same edit twice
23030                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23031            )))
23032        });
23033    let rename_task = cx
23034        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23035        .expect("Confirm rename was not started");
23036    rename_handler.next().await.unwrap();
23037    rename_task.await.expect("Confirm rename failed");
23038    cx.run_until_parked();
23039
23040    // Despite two edits, only one is actually applied as those are identical
23041    cx.assert_editor_state(indoc! {"
23042        struct FooRenamedˇ {}
23043    "});
23044}
23045
23046#[gpui::test]
23047async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23048    init_test(cx, |_| {});
23049    // These capabilities indicate that the server does not support prepare rename.
23050    let capabilities = lsp::ServerCapabilities {
23051        rename_provider: Some(lsp::OneOf::Left(true)),
23052        ..Default::default()
23053    };
23054    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23055
23056    cx.set_state(indoc! {"
23057        struct Fˇoo {}
23058    "});
23059
23060    cx.update_editor(|editor, _window, cx| {
23061        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23062        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23063        editor.highlight_background::<DocumentHighlightRead>(
23064            &[highlight_range],
23065            |theme| theme.colors().editor_document_highlight_read_background,
23066            cx,
23067        );
23068    });
23069
23070    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23071        .expect("Prepare rename was not started")
23072        .await
23073        .expect("Prepare rename failed");
23074
23075    let mut rename_handler =
23076        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23077            let edit = lsp::TextEdit {
23078                range: lsp::Range {
23079                    start: lsp::Position {
23080                        line: 0,
23081                        character: 7,
23082                    },
23083                    end: lsp::Position {
23084                        line: 0,
23085                        character: 10,
23086                    },
23087                },
23088                new_text: "FooRenamed".to_string(),
23089            };
23090            Ok(Some(lsp::WorkspaceEdit::new(
23091                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23092            )))
23093        });
23094    let rename_task = cx
23095        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23096        .expect("Confirm rename was not started");
23097    rename_handler.next().await.unwrap();
23098    rename_task.await.expect("Confirm rename failed");
23099    cx.run_until_parked();
23100
23101    // Correct range is renamed, as `surrounding_word` is used to find it.
23102    cx.assert_editor_state(indoc! {"
23103        struct FooRenamedˇ {}
23104    "});
23105}
23106
23107#[gpui::test]
23108async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23109    init_test(cx, |_| {});
23110    let mut cx = EditorTestContext::new(cx).await;
23111
23112    let language = Arc::new(
23113        Language::new(
23114            LanguageConfig::default(),
23115            Some(tree_sitter_html::LANGUAGE.into()),
23116        )
23117        .with_brackets_query(
23118            r#"
23119            ("<" @open "/>" @close)
23120            ("</" @open ">" @close)
23121            ("<" @open ">" @close)
23122            ("\"" @open "\"" @close)
23123            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23124        "#,
23125        )
23126        .unwrap(),
23127    );
23128    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23129
23130    cx.set_state(indoc! {"
23131        <span>ˇ</span>
23132    "});
23133    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23134    cx.assert_editor_state(indoc! {"
23135        <span>
23136        ˇ
23137        </span>
23138    "});
23139
23140    cx.set_state(indoc! {"
23141        <span><span></span>ˇ</span>
23142    "});
23143    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23144    cx.assert_editor_state(indoc! {"
23145        <span><span></span>
23146        ˇ</span>
23147    "});
23148
23149    cx.set_state(indoc! {"
23150        <span>ˇ
23151        </span>
23152    "});
23153    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23154    cx.assert_editor_state(indoc! {"
23155        <span>
23156        ˇ
23157        </span>
23158    "});
23159}
23160
23161#[gpui::test(iterations = 10)]
23162async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23163    init_test(cx, |_| {});
23164
23165    let fs = FakeFs::new(cx.executor());
23166    fs.insert_tree(
23167        path!("/dir"),
23168        json!({
23169            "a.ts": "a",
23170        }),
23171    )
23172    .await;
23173
23174    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23175    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23176    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23177
23178    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23179    language_registry.add(Arc::new(Language::new(
23180        LanguageConfig {
23181            name: "TypeScript".into(),
23182            matcher: LanguageMatcher {
23183                path_suffixes: vec!["ts".to_string()],
23184                ..Default::default()
23185            },
23186            ..Default::default()
23187        },
23188        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23189    )));
23190    let mut fake_language_servers = language_registry.register_fake_lsp(
23191        "TypeScript",
23192        FakeLspAdapter {
23193            capabilities: lsp::ServerCapabilities {
23194                code_lens_provider: Some(lsp::CodeLensOptions {
23195                    resolve_provider: Some(true),
23196                }),
23197                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23198                    commands: vec!["_the/command".to_string()],
23199                    ..lsp::ExecuteCommandOptions::default()
23200                }),
23201                ..lsp::ServerCapabilities::default()
23202            },
23203            ..FakeLspAdapter::default()
23204        },
23205    );
23206
23207    let editor = workspace
23208        .update(cx, |workspace, window, cx| {
23209            workspace.open_abs_path(
23210                PathBuf::from(path!("/dir/a.ts")),
23211                OpenOptions::default(),
23212                window,
23213                cx,
23214            )
23215        })
23216        .unwrap()
23217        .await
23218        .unwrap()
23219        .downcast::<Editor>()
23220        .unwrap();
23221    cx.executor().run_until_parked();
23222
23223    let fake_server = fake_language_servers.next().await.unwrap();
23224
23225    let buffer = editor.update(cx, |editor, cx| {
23226        editor
23227            .buffer()
23228            .read(cx)
23229            .as_singleton()
23230            .expect("have opened a single file by path")
23231    });
23232
23233    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23234    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23235    drop(buffer_snapshot);
23236    let actions = cx
23237        .update_window(*workspace, |_, window, cx| {
23238            project.code_actions(&buffer, anchor..anchor, window, cx)
23239        })
23240        .unwrap();
23241
23242    fake_server
23243        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23244            Ok(Some(vec![
23245                lsp::CodeLens {
23246                    range: lsp::Range::default(),
23247                    command: Some(lsp::Command {
23248                        title: "Code lens command".to_owned(),
23249                        command: "_the/command".to_owned(),
23250                        arguments: None,
23251                    }),
23252                    data: None,
23253                },
23254                lsp::CodeLens {
23255                    range: lsp::Range::default(),
23256                    command: Some(lsp::Command {
23257                        title: "Command not in capabilities".to_owned(),
23258                        command: "not in capabilities".to_owned(),
23259                        arguments: None,
23260                    }),
23261                    data: None,
23262                },
23263                lsp::CodeLens {
23264                    range: lsp::Range {
23265                        start: lsp::Position {
23266                            line: 1,
23267                            character: 1,
23268                        },
23269                        end: lsp::Position {
23270                            line: 1,
23271                            character: 1,
23272                        },
23273                    },
23274                    command: Some(lsp::Command {
23275                        title: "Command not in range".to_owned(),
23276                        command: "_the/command".to_owned(),
23277                        arguments: None,
23278                    }),
23279                    data: None,
23280                },
23281            ]))
23282        })
23283        .next()
23284        .await;
23285
23286    let actions = actions.await.unwrap();
23287    assert_eq!(
23288        actions.len(),
23289        1,
23290        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23291    );
23292    let action = actions[0].clone();
23293    let apply = project.update(cx, |project, cx| {
23294        project.apply_code_action(buffer.clone(), action, true, cx)
23295    });
23296
23297    // Resolving the code action does not populate its edits. In absence of
23298    // edits, we must execute the given command.
23299    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23300        |mut lens, _| async move {
23301            let lens_command = lens.command.as_mut().expect("should have a command");
23302            assert_eq!(lens_command.title, "Code lens command");
23303            lens_command.arguments = Some(vec![json!("the-argument")]);
23304            Ok(lens)
23305        },
23306    );
23307
23308    // While executing the command, the language server sends the editor
23309    // a `workspaceEdit` request.
23310    fake_server
23311        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23312            let fake = fake_server.clone();
23313            move |params, _| {
23314                assert_eq!(params.command, "_the/command");
23315                let fake = fake.clone();
23316                async move {
23317                    fake.server
23318                        .request::<lsp::request::ApplyWorkspaceEdit>(
23319                            lsp::ApplyWorkspaceEditParams {
23320                                label: None,
23321                                edit: lsp::WorkspaceEdit {
23322                                    changes: Some(
23323                                        [(
23324                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23325                                            vec![lsp::TextEdit {
23326                                                range: lsp::Range::new(
23327                                                    lsp::Position::new(0, 0),
23328                                                    lsp::Position::new(0, 0),
23329                                                ),
23330                                                new_text: "X".into(),
23331                                            }],
23332                                        )]
23333                                        .into_iter()
23334                                        .collect(),
23335                                    ),
23336                                    ..lsp::WorkspaceEdit::default()
23337                                },
23338                            },
23339                        )
23340                        .await
23341                        .into_response()
23342                        .unwrap();
23343                    Ok(Some(json!(null)))
23344                }
23345            }
23346        })
23347        .next()
23348        .await;
23349
23350    // Applying the code lens command returns a project transaction containing the edits
23351    // sent by the language server in its `workspaceEdit` request.
23352    let transaction = apply.await.unwrap();
23353    assert!(transaction.0.contains_key(&buffer));
23354    buffer.update(cx, |buffer, cx| {
23355        assert_eq!(buffer.text(), "Xa");
23356        buffer.undo(cx);
23357        assert_eq!(buffer.text(), "a");
23358    });
23359
23360    let actions_after_edits = cx
23361        .update_window(*workspace, |_, window, cx| {
23362            project.code_actions(&buffer, anchor..anchor, window, cx)
23363        })
23364        .unwrap()
23365        .await
23366        .unwrap();
23367    assert_eq!(
23368        actions, actions_after_edits,
23369        "For the same selection, same code lens actions should be returned"
23370    );
23371
23372    let _responses =
23373        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23374            panic!("No more code lens requests are expected");
23375        });
23376    editor.update_in(cx, |editor, window, cx| {
23377        editor.select_all(&SelectAll, window, cx);
23378    });
23379    cx.executor().run_until_parked();
23380    let new_actions = cx
23381        .update_window(*workspace, |_, window, cx| {
23382            project.code_actions(&buffer, anchor..anchor, window, cx)
23383        })
23384        .unwrap()
23385        .await
23386        .unwrap();
23387    assert_eq!(
23388        actions, new_actions,
23389        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23390    );
23391}
23392
23393#[gpui::test]
23394async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23395    init_test(cx, |_| {});
23396
23397    let fs = FakeFs::new(cx.executor());
23398    let main_text = r#"fn main() {
23399println!("1");
23400println!("2");
23401println!("3");
23402println!("4");
23403println!("5");
23404}"#;
23405    let lib_text = "mod foo {}";
23406    fs.insert_tree(
23407        path!("/a"),
23408        json!({
23409            "lib.rs": lib_text,
23410            "main.rs": main_text,
23411        }),
23412    )
23413    .await;
23414
23415    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23416    let (workspace, cx) =
23417        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23418    let worktree_id = workspace.update(cx, |workspace, cx| {
23419        workspace.project().update(cx, |project, cx| {
23420            project.worktrees(cx).next().unwrap().read(cx).id()
23421        })
23422    });
23423
23424    let expected_ranges = vec![
23425        Point::new(0, 0)..Point::new(0, 0),
23426        Point::new(1, 0)..Point::new(1, 1),
23427        Point::new(2, 0)..Point::new(2, 2),
23428        Point::new(3, 0)..Point::new(3, 3),
23429    ];
23430
23431    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23432    let editor_1 = workspace
23433        .update_in(cx, |workspace, window, cx| {
23434            workspace.open_path(
23435                (worktree_id, "main.rs"),
23436                Some(pane_1.downgrade()),
23437                true,
23438                window,
23439                cx,
23440            )
23441        })
23442        .unwrap()
23443        .await
23444        .downcast::<Editor>()
23445        .unwrap();
23446    pane_1.update(cx, |pane, cx| {
23447        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23448        open_editor.update(cx, |editor, cx| {
23449            assert_eq!(
23450                editor.display_text(cx),
23451                main_text,
23452                "Original main.rs text on initial open",
23453            );
23454            assert_eq!(
23455                editor
23456                    .selections
23457                    .all::<Point>(cx)
23458                    .into_iter()
23459                    .map(|s| s.range())
23460                    .collect::<Vec<_>>(),
23461                vec![Point::zero()..Point::zero()],
23462                "Default selections on initial open",
23463            );
23464        })
23465    });
23466    editor_1.update_in(cx, |editor, window, cx| {
23467        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23468            s.select_ranges(expected_ranges.clone());
23469        });
23470    });
23471
23472    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23473        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23474    });
23475    let editor_2 = workspace
23476        .update_in(cx, |workspace, window, cx| {
23477            workspace.open_path(
23478                (worktree_id, "main.rs"),
23479                Some(pane_2.downgrade()),
23480                true,
23481                window,
23482                cx,
23483            )
23484        })
23485        .unwrap()
23486        .await
23487        .downcast::<Editor>()
23488        .unwrap();
23489    pane_2.update(cx, |pane, cx| {
23490        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23491        open_editor.update(cx, |editor, cx| {
23492            assert_eq!(
23493                editor.display_text(cx),
23494                main_text,
23495                "Original main.rs text on initial open in another panel",
23496            );
23497            assert_eq!(
23498                editor
23499                    .selections
23500                    .all::<Point>(cx)
23501                    .into_iter()
23502                    .map(|s| s.range())
23503                    .collect::<Vec<_>>(),
23504                vec![Point::zero()..Point::zero()],
23505                "Default selections on initial open in another panel",
23506            );
23507        })
23508    });
23509
23510    editor_2.update_in(cx, |editor, window, cx| {
23511        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23512    });
23513
23514    let _other_editor_1 = workspace
23515        .update_in(cx, |workspace, window, cx| {
23516            workspace.open_path(
23517                (worktree_id, "lib.rs"),
23518                Some(pane_1.downgrade()),
23519                true,
23520                window,
23521                cx,
23522            )
23523        })
23524        .unwrap()
23525        .await
23526        .downcast::<Editor>()
23527        .unwrap();
23528    pane_1
23529        .update_in(cx, |pane, window, cx| {
23530            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23531        })
23532        .await
23533        .unwrap();
23534    drop(editor_1);
23535    pane_1.update(cx, |pane, cx| {
23536        pane.active_item()
23537            .unwrap()
23538            .downcast::<Editor>()
23539            .unwrap()
23540            .update(cx, |editor, cx| {
23541                assert_eq!(
23542                    editor.display_text(cx),
23543                    lib_text,
23544                    "Other file should be open and active",
23545                );
23546            });
23547        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23548    });
23549
23550    let _other_editor_2 = workspace
23551        .update_in(cx, |workspace, window, cx| {
23552            workspace.open_path(
23553                (worktree_id, "lib.rs"),
23554                Some(pane_2.downgrade()),
23555                true,
23556                window,
23557                cx,
23558            )
23559        })
23560        .unwrap()
23561        .await
23562        .downcast::<Editor>()
23563        .unwrap();
23564    pane_2
23565        .update_in(cx, |pane, window, cx| {
23566            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23567        })
23568        .await
23569        .unwrap();
23570    drop(editor_2);
23571    pane_2.update(cx, |pane, cx| {
23572        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23573        open_editor.update(cx, |editor, cx| {
23574            assert_eq!(
23575                editor.display_text(cx),
23576                lib_text,
23577                "Other file should be open and active in another panel too",
23578            );
23579        });
23580        assert_eq!(
23581            pane.items().count(),
23582            1,
23583            "No other editors should be open in another pane",
23584        );
23585    });
23586
23587    let _editor_1_reopened = workspace
23588        .update_in(cx, |workspace, window, cx| {
23589            workspace.open_path(
23590                (worktree_id, "main.rs"),
23591                Some(pane_1.downgrade()),
23592                true,
23593                window,
23594                cx,
23595            )
23596        })
23597        .unwrap()
23598        .await
23599        .downcast::<Editor>()
23600        .unwrap();
23601    let _editor_2_reopened = workspace
23602        .update_in(cx, |workspace, window, cx| {
23603            workspace.open_path(
23604                (worktree_id, "main.rs"),
23605                Some(pane_2.downgrade()),
23606                true,
23607                window,
23608                cx,
23609            )
23610        })
23611        .unwrap()
23612        .await
23613        .downcast::<Editor>()
23614        .unwrap();
23615    pane_1.update(cx, |pane, cx| {
23616        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23617        open_editor.update(cx, |editor, cx| {
23618            assert_eq!(
23619                editor.display_text(cx),
23620                main_text,
23621                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23622            );
23623            assert_eq!(
23624                editor
23625                    .selections
23626                    .all::<Point>(cx)
23627                    .into_iter()
23628                    .map(|s| s.range())
23629                    .collect::<Vec<_>>(),
23630                expected_ranges,
23631                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23632            );
23633        })
23634    });
23635    pane_2.update(cx, |pane, cx| {
23636        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23637        open_editor.update(cx, |editor, cx| {
23638            assert_eq!(
23639                editor.display_text(cx),
23640                r#"fn main() {
23641⋯rintln!("1");
23642⋯intln!("2");
23643⋯ntln!("3");
23644println!("4");
23645println!("5");
23646}"#,
23647                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23648            );
23649            assert_eq!(
23650                editor
23651                    .selections
23652                    .all::<Point>(cx)
23653                    .into_iter()
23654                    .map(|s| s.range())
23655                    .collect::<Vec<_>>(),
23656                vec![Point::zero()..Point::zero()],
23657                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23658            );
23659        })
23660    });
23661}
23662
23663#[gpui::test]
23664async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23665    init_test(cx, |_| {});
23666
23667    let fs = FakeFs::new(cx.executor());
23668    let main_text = r#"fn main() {
23669println!("1");
23670println!("2");
23671println!("3");
23672println!("4");
23673println!("5");
23674}"#;
23675    let lib_text = "mod foo {}";
23676    fs.insert_tree(
23677        path!("/a"),
23678        json!({
23679            "lib.rs": lib_text,
23680            "main.rs": main_text,
23681        }),
23682    )
23683    .await;
23684
23685    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23686    let (workspace, cx) =
23687        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23688    let worktree_id = workspace.update(cx, |workspace, cx| {
23689        workspace.project().update(cx, |project, cx| {
23690            project.worktrees(cx).next().unwrap().read(cx).id()
23691        })
23692    });
23693
23694    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23695    let editor = workspace
23696        .update_in(cx, |workspace, window, cx| {
23697            workspace.open_path(
23698                (worktree_id, "main.rs"),
23699                Some(pane.downgrade()),
23700                true,
23701                window,
23702                cx,
23703            )
23704        })
23705        .unwrap()
23706        .await
23707        .downcast::<Editor>()
23708        .unwrap();
23709    pane.update(cx, |pane, cx| {
23710        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23711        open_editor.update(cx, |editor, cx| {
23712            assert_eq!(
23713                editor.display_text(cx),
23714                main_text,
23715                "Original main.rs text on initial open",
23716            );
23717        })
23718    });
23719    editor.update_in(cx, |editor, window, cx| {
23720        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23721    });
23722
23723    cx.update_global(|store: &mut SettingsStore, cx| {
23724        store.update_user_settings(cx, |s| {
23725            s.workspace.restore_on_file_reopen = Some(false);
23726        });
23727    });
23728    editor.update_in(cx, |editor, window, cx| {
23729        editor.fold_ranges(
23730            vec![
23731                Point::new(1, 0)..Point::new(1, 1),
23732                Point::new(2, 0)..Point::new(2, 2),
23733                Point::new(3, 0)..Point::new(3, 3),
23734            ],
23735            false,
23736            window,
23737            cx,
23738        );
23739    });
23740    pane.update_in(cx, |pane, window, cx| {
23741        pane.close_all_items(&CloseAllItems::default(), window, cx)
23742    })
23743    .await
23744    .unwrap();
23745    pane.update(cx, |pane, _| {
23746        assert!(pane.active_item().is_none());
23747    });
23748    cx.update_global(|store: &mut SettingsStore, cx| {
23749        store.update_user_settings(cx, |s| {
23750            s.workspace.restore_on_file_reopen = Some(true);
23751        });
23752    });
23753
23754    let _editor_reopened = workspace
23755        .update_in(cx, |workspace, window, cx| {
23756            workspace.open_path(
23757                (worktree_id, "main.rs"),
23758                Some(pane.downgrade()),
23759                true,
23760                window,
23761                cx,
23762            )
23763        })
23764        .unwrap()
23765        .await
23766        .downcast::<Editor>()
23767        .unwrap();
23768    pane.update(cx, |pane, cx| {
23769        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23770        open_editor.update(cx, |editor, cx| {
23771            assert_eq!(
23772                editor.display_text(cx),
23773                main_text,
23774                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23775            );
23776        })
23777    });
23778}
23779
23780#[gpui::test]
23781async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23782    struct EmptyModalView {
23783        focus_handle: gpui::FocusHandle,
23784    }
23785    impl EventEmitter<DismissEvent> for EmptyModalView {}
23786    impl Render for EmptyModalView {
23787        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23788            div()
23789        }
23790    }
23791    impl Focusable for EmptyModalView {
23792        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23793            self.focus_handle.clone()
23794        }
23795    }
23796    impl workspace::ModalView for EmptyModalView {}
23797    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23798        EmptyModalView {
23799            focus_handle: cx.focus_handle(),
23800        }
23801    }
23802
23803    init_test(cx, |_| {});
23804
23805    let fs = FakeFs::new(cx.executor());
23806    let project = Project::test(fs, [], cx).await;
23807    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23808    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23809    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23810    let editor = cx.new_window_entity(|window, cx| {
23811        Editor::new(
23812            EditorMode::full(),
23813            buffer,
23814            Some(project.clone()),
23815            window,
23816            cx,
23817        )
23818    });
23819    workspace
23820        .update(cx, |workspace, window, cx| {
23821            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23822        })
23823        .unwrap();
23824    editor.update_in(cx, |editor, window, cx| {
23825        editor.open_context_menu(&OpenContextMenu, window, cx);
23826        assert!(editor.mouse_context_menu.is_some());
23827    });
23828    workspace
23829        .update(cx, |workspace, window, cx| {
23830            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23831        })
23832        .unwrap();
23833    cx.read(|cx| {
23834        assert!(editor.read(cx).mouse_context_menu.is_none());
23835    });
23836}
23837
23838fn set_linked_edit_ranges(
23839    opening: (Point, Point),
23840    closing: (Point, Point),
23841    editor: &mut Editor,
23842    cx: &mut Context<Editor>,
23843) {
23844    let Some((buffer, _)) = editor
23845        .buffer
23846        .read(cx)
23847        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
23848    else {
23849        panic!("Failed to get buffer for selection position");
23850    };
23851    let buffer = buffer.read(cx);
23852    let buffer_id = buffer.remote_id();
23853    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
23854    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
23855    let mut linked_ranges = HashMap::default();
23856    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
23857    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
23858}
23859
23860#[gpui::test]
23861async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
23862    init_test(cx, |_| {});
23863
23864    let fs = FakeFs::new(cx.executor());
23865    fs.insert_file(path!("/file.html"), Default::default())
23866        .await;
23867
23868    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
23869
23870    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23871    let html_language = Arc::new(Language::new(
23872        LanguageConfig {
23873            name: "HTML".into(),
23874            matcher: LanguageMatcher {
23875                path_suffixes: vec!["html".to_string()],
23876                ..LanguageMatcher::default()
23877            },
23878            brackets: BracketPairConfig {
23879                pairs: vec![BracketPair {
23880                    start: "<".into(),
23881                    end: ">".into(),
23882                    close: true,
23883                    ..Default::default()
23884                }],
23885                ..Default::default()
23886            },
23887            ..Default::default()
23888        },
23889        Some(tree_sitter_html::LANGUAGE.into()),
23890    ));
23891    language_registry.add(html_language);
23892    let mut fake_servers = language_registry.register_fake_lsp(
23893        "HTML",
23894        FakeLspAdapter {
23895            capabilities: lsp::ServerCapabilities {
23896                completion_provider: Some(lsp::CompletionOptions {
23897                    resolve_provider: Some(true),
23898                    ..Default::default()
23899                }),
23900                ..Default::default()
23901            },
23902            ..Default::default()
23903        },
23904    );
23905
23906    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23907    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23908
23909    let worktree_id = workspace
23910        .update(cx, |workspace, _window, cx| {
23911            workspace.project().update(cx, |project, cx| {
23912                project.worktrees(cx).next().unwrap().read(cx).id()
23913            })
23914        })
23915        .unwrap();
23916    project
23917        .update(cx, |project, cx| {
23918            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
23919        })
23920        .await
23921        .unwrap();
23922    let editor = workspace
23923        .update(cx, |workspace, window, cx| {
23924            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
23925        })
23926        .unwrap()
23927        .await
23928        .unwrap()
23929        .downcast::<Editor>()
23930        .unwrap();
23931
23932    let fake_server = fake_servers.next().await.unwrap();
23933    editor.update_in(cx, |editor, window, cx| {
23934        editor.set_text("<ad></ad>", window, cx);
23935        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23936            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
23937        });
23938        set_linked_edit_ranges(
23939            (Point::new(0, 1), Point::new(0, 3)),
23940            (Point::new(0, 6), Point::new(0, 8)),
23941            editor,
23942            cx,
23943        );
23944    });
23945    let mut completion_handle =
23946        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
23947            Ok(Some(lsp::CompletionResponse::Array(vec![
23948                lsp::CompletionItem {
23949                    label: "head".to_string(),
23950                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23951                        lsp::InsertReplaceEdit {
23952                            new_text: "head".to_string(),
23953                            insert: lsp::Range::new(
23954                                lsp::Position::new(0, 1),
23955                                lsp::Position::new(0, 3),
23956                            ),
23957                            replace: lsp::Range::new(
23958                                lsp::Position::new(0, 1),
23959                                lsp::Position::new(0, 3),
23960                            ),
23961                        },
23962                    )),
23963                    ..Default::default()
23964                },
23965            ])))
23966        });
23967    editor.update_in(cx, |editor, window, cx| {
23968        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
23969    });
23970    cx.run_until_parked();
23971    completion_handle.next().await.unwrap();
23972    editor.update(cx, |editor, _| {
23973        assert!(
23974            editor.context_menu_visible(),
23975            "Completion menu should be visible"
23976        );
23977    });
23978    editor.update_in(cx, |editor, window, cx| {
23979        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
23980    });
23981    cx.executor().run_until_parked();
23982    editor.update(cx, |editor, cx| {
23983        assert_eq!(editor.text(cx), "<head></head>");
23984    });
23985}
23986
23987#[gpui::test]
23988async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
23989    init_test(cx, |_| {});
23990
23991    let mut cx = EditorTestContext::new(cx).await;
23992    let language = Arc::new(Language::new(
23993        LanguageConfig {
23994            name: "TSX".into(),
23995            matcher: LanguageMatcher {
23996                path_suffixes: vec!["tsx".to_string()],
23997                ..LanguageMatcher::default()
23998            },
23999            brackets: BracketPairConfig {
24000                pairs: vec![BracketPair {
24001                    start: "<".into(),
24002                    end: ">".into(),
24003                    close: true,
24004                    ..Default::default()
24005                }],
24006                ..Default::default()
24007            },
24008            linked_edit_characters: HashSet::from_iter(['.']),
24009            ..Default::default()
24010        },
24011        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24012    ));
24013    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24014
24015    // Test typing > does not extend linked pair
24016    cx.set_state("<divˇ<div></div>");
24017    cx.update_editor(|editor, _, cx| {
24018        set_linked_edit_ranges(
24019            (Point::new(0, 1), Point::new(0, 4)),
24020            (Point::new(0, 11), Point::new(0, 14)),
24021            editor,
24022            cx,
24023        );
24024    });
24025    cx.update_editor(|editor, window, cx| {
24026        editor.handle_input(">", window, cx);
24027    });
24028    cx.assert_editor_state("<div>ˇ<div></div>");
24029
24030    // Test typing . do extend linked pair
24031    cx.set_state("<Animatedˇ></Animated>");
24032    cx.update_editor(|editor, _, cx| {
24033        set_linked_edit_ranges(
24034            (Point::new(0, 1), Point::new(0, 9)),
24035            (Point::new(0, 12), Point::new(0, 20)),
24036            editor,
24037            cx,
24038        );
24039    });
24040    cx.update_editor(|editor, window, cx| {
24041        editor.handle_input(".", window, cx);
24042    });
24043    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24044    cx.update_editor(|editor, _, cx| {
24045        set_linked_edit_ranges(
24046            (Point::new(0, 1), Point::new(0, 10)),
24047            (Point::new(0, 13), Point::new(0, 21)),
24048            editor,
24049            cx,
24050        );
24051    });
24052    cx.update_editor(|editor, window, cx| {
24053        editor.handle_input("V", window, cx);
24054    });
24055    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24056}
24057
24058#[gpui::test]
24059async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24060    init_test(cx, |_| {});
24061
24062    let fs = FakeFs::new(cx.executor());
24063    fs.insert_tree(
24064        path!("/root"),
24065        json!({
24066            "a": {
24067                "main.rs": "fn main() {}",
24068            },
24069            "foo": {
24070                "bar": {
24071                    "external_file.rs": "pub mod external {}",
24072                }
24073            }
24074        }),
24075    )
24076    .await;
24077
24078    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24079    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24080    language_registry.add(rust_lang());
24081    let _fake_servers = language_registry.register_fake_lsp(
24082        "Rust",
24083        FakeLspAdapter {
24084            ..FakeLspAdapter::default()
24085        },
24086    );
24087    let (workspace, cx) =
24088        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24089    let worktree_id = workspace.update(cx, |workspace, cx| {
24090        workspace.project().update(cx, |project, cx| {
24091            project.worktrees(cx).next().unwrap().read(cx).id()
24092        })
24093    });
24094
24095    let assert_language_servers_count =
24096        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24097            project.update(cx, |project, cx| {
24098                let current = project
24099                    .lsp_store()
24100                    .read(cx)
24101                    .as_local()
24102                    .unwrap()
24103                    .language_servers
24104                    .len();
24105                assert_eq!(expected, current, "{context}");
24106            });
24107        };
24108
24109    assert_language_servers_count(
24110        0,
24111        "No servers should be running before any file is open",
24112        cx,
24113    );
24114    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24115    let main_editor = workspace
24116        .update_in(cx, |workspace, window, cx| {
24117            workspace.open_path(
24118                (worktree_id, "main.rs"),
24119                Some(pane.downgrade()),
24120                true,
24121                window,
24122                cx,
24123            )
24124        })
24125        .unwrap()
24126        .await
24127        .downcast::<Editor>()
24128        .unwrap();
24129    pane.update(cx, |pane, cx| {
24130        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24131        open_editor.update(cx, |editor, cx| {
24132            assert_eq!(
24133                editor.display_text(cx),
24134                "fn main() {}",
24135                "Original main.rs text on initial open",
24136            );
24137        });
24138        assert_eq!(open_editor, main_editor);
24139    });
24140    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24141
24142    let external_editor = workspace
24143        .update_in(cx, |workspace, window, cx| {
24144            workspace.open_abs_path(
24145                PathBuf::from("/root/foo/bar/external_file.rs"),
24146                OpenOptions::default(),
24147                window,
24148                cx,
24149            )
24150        })
24151        .await
24152        .expect("opening external file")
24153        .downcast::<Editor>()
24154        .expect("downcasted external file's open element to editor");
24155    pane.update(cx, |pane, cx| {
24156        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24157        open_editor.update(cx, |editor, cx| {
24158            assert_eq!(
24159                editor.display_text(cx),
24160                "pub mod external {}",
24161                "External file is open now",
24162            );
24163        });
24164        assert_eq!(open_editor, external_editor);
24165    });
24166    assert_language_servers_count(
24167        1,
24168        "Second, external, *.rs file should join the existing server",
24169        cx,
24170    );
24171
24172    pane.update_in(cx, |pane, window, cx| {
24173        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24174    })
24175    .await
24176    .unwrap();
24177    pane.update_in(cx, |pane, window, cx| {
24178        pane.navigate_backward(&Default::default(), window, cx);
24179    });
24180    cx.run_until_parked();
24181    pane.update(cx, |pane, cx| {
24182        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24183        open_editor.update(cx, |editor, cx| {
24184            assert_eq!(
24185                editor.display_text(cx),
24186                "pub mod external {}",
24187                "External file is open now",
24188            );
24189        });
24190    });
24191    assert_language_servers_count(
24192        1,
24193        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24194        cx,
24195    );
24196
24197    cx.update(|_, cx| {
24198        workspace::reload(cx);
24199    });
24200    assert_language_servers_count(
24201        1,
24202        "After reloading the worktree with local and external files opened, only one project should be started",
24203        cx,
24204    );
24205}
24206
24207#[gpui::test]
24208async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24209    init_test(cx, |_| {});
24210
24211    let mut cx = EditorTestContext::new(cx).await;
24212    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24213    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24214
24215    // test cursor move to start of each line on tab
24216    // for `if`, `elif`, `else`, `while`, `with` and `for`
24217    cx.set_state(indoc! {"
24218        def main():
24219        ˇ    for item in items:
24220        ˇ        while item.active:
24221        ˇ            if item.value > 10:
24222        ˇ                continue
24223        ˇ            elif item.value < 0:
24224        ˇ                break
24225        ˇ            else:
24226        ˇ                with item.context() as ctx:
24227        ˇ                    yield count
24228        ˇ        else:
24229        ˇ            log('while else')
24230        ˇ    else:
24231        ˇ        log('for else')
24232    "});
24233    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24234    cx.assert_editor_state(indoc! {"
24235        def main():
24236            ˇfor item in items:
24237                ˇwhile item.active:
24238                    ˇif item.value > 10:
24239                        ˇcontinue
24240                    ˇelif item.value < 0:
24241                        ˇbreak
24242                    ˇelse:
24243                        ˇwith item.context() as ctx:
24244                            ˇyield count
24245                ˇelse:
24246                    ˇlog('while else')
24247            ˇelse:
24248                ˇlog('for else')
24249    "});
24250    // test relative indent is preserved when tab
24251    // for `if`, `elif`, `else`, `while`, `with` and `for`
24252    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24253    cx.assert_editor_state(indoc! {"
24254        def main():
24255                ˇfor item in items:
24256                    ˇwhile item.active:
24257                        ˇif item.value > 10:
24258                            ˇcontinue
24259                        ˇelif item.value < 0:
24260                            ˇbreak
24261                        ˇelse:
24262                            ˇwith item.context() as ctx:
24263                                ˇyield count
24264                    ˇelse:
24265                        ˇlog('while else')
24266                ˇelse:
24267                    ˇlog('for else')
24268    "});
24269
24270    // test cursor move to start of each line on tab
24271    // for `try`, `except`, `else`, `finally`, `match` and `def`
24272    cx.set_state(indoc! {"
24273        def main():
24274        ˇ    try:
24275        ˇ        fetch()
24276        ˇ    except ValueError:
24277        ˇ        handle_error()
24278        ˇ    else:
24279        ˇ        match value:
24280        ˇ            case _:
24281        ˇ    finally:
24282        ˇ        def status():
24283        ˇ            return 0
24284    "});
24285    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24286    cx.assert_editor_state(indoc! {"
24287        def main():
24288            ˇtry:
24289                ˇfetch()
24290            ˇexcept ValueError:
24291                ˇhandle_error()
24292            ˇelse:
24293                ˇmatch value:
24294                    ˇcase _:
24295            ˇfinally:
24296                ˇdef status():
24297                    ˇreturn 0
24298    "});
24299    // test relative indent is preserved when tab
24300    // for `try`, `except`, `else`, `finally`, `match` and `def`
24301    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24302    cx.assert_editor_state(indoc! {"
24303        def main():
24304                ˇtry:
24305                    ˇfetch()
24306                ˇexcept ValueError:
24307                    ˇhandle_error()
24308                ˇelse:
24309                    ˇmatch value:
24310                        ˇcase _:
24311                ˇfinally:
24312                    ˇdef status():
24313                        ˇreturn 0
24314    "});
24315}
24316
24317#[gpui::test]
24318async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24319    init_test(cx, |_| {});
24320
24321    let mut cx = EditorTestContext::new(cx).await;
24322    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24323    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24324
24325    // test `else` auto outdents when typed inside `if` block
24326    cx.set_state(indoc! {"
24327        def main():
24328            if i == 2:
24329                return
24330                ˇ
24331    "});
24332    cx.update_editor(|editor, window, cx| {
24333        editor.handle_input("else:", window, cx);
24334    });
24335    cx.assert_editor_state(indoc! {"
24336        def main():
24337            if i == 2:
24338                return
24339            else:ˇ
24340    "});
24341
24342    // test `except` auto outdents when typed inside `try` block
24343    cx.set_state(indoc! {"
24344        def main():
24345            try:
24346                i = 2
24347                ˇ
24348    "});
24349    cx.update_editor(|editor, window, cx| {
24350        editor.handle_input("except:", window, cx);
24351    });
24352    cx.assert_editor_state(indoc! {"
24353        def main():
24354            try:
24355                i = 2
24356            except:ˇ
24357    "});
24358
24359    // test `else` auto outdents when typed inside `except` block
24360    cx.set_state(indoc! {"
24361        def main():
24362            try:
24363                i = 2
24364            except:
24365                j = 2
24366                ˇ
24367    "});
24368    cx.update_editor(|editor, window, cx| {
24369        editor.handle_input("else:", window, cx);
24370    });
24371    cx.assert_editor_state(indoc! {"
24372        def main():
24373            try:
24374                i = 2
24375            except:
24376                j = 2
24377            else:ˇ
24378    "});
24379
24380    // test `finally` auto outdents when typed inside `else` block
24381    cx.set_state(indoc! {"
24382        def main():
24383            try:
24384                i = 2
24385            except:
24386                j = 2
24387            else:
24388                k = 2
24389                ˇ
24390    "});
24391    cx.update_editor(|editor, window, cx| {
24392        editor.handle_input("finally:", window, cx);
24393    });
24394    cx.assert_editor_state(indoc! {"
24395        def main():
24396            try:
24397                i = 2
24398            except:
24399                j = 2
24400            else:
24401                k = 2
24402            finally:ˇ
24403    "});
24404
24405    // test `else` does not outdents when typed inside `except` block right after for block
24406    cx.set_state(indoc! {"
24407        def main():
24408            try:
24409                i = 2
24410            except:
24411                for i in range(n):
24412                    pass
24413                ˇ
24414    "});
24415    cx.update_editor(|editor, window, cx| {
24416        editor.handle_input("else:", window, cx);
24417    });
24418    cx.assert_editor_state(indoc! {"
24419        def main():
24420            try:
24421                i = 2
24422            except:
24423                for i in range(n):
24424                    pass
24425                else:ˇ
24426    "});
24427
24428    // test `finally` auto outdents when typed inside `else` block right after for block
24429    cx.set_state(indoc! {"
24430        def main():
24431            try:
24432                i = 2
24433            except:
24434                j = 2
24435            else:
24436                for i in range(n):
24437                    pass
24438                ˇ
24439    "});
24440    cx.update_editor(|editor, window, cx| {
24441        editor.handle_input("finally:", window, cx);
24442    });
24443    cx.assert_editor_state(indoc! {"
24444        def main():
24445            try:
24446                i = 2
24447            except:
24448                j = 2
24449            else:
24450                for i in range(n):
24451                    pass
24452            finally:ˇ
24453    "});
24454
24455    // test `except` outdents to inner "try" block
24456    cx.set_state(indoc! {"
24457        def main():
24458            try:
24459                i = 2
24460                if i == 2:
24461                    try:
24462                        i = 3
24463                        ˇ
24464    "});
24465    cx.update_editor(|editor, window, cx| {
24466        editor.handle_input("except:", window, cx);
24467    });
24468    cx.assert_editor_state(indoc! {"
24469        def main():
24470            try:
24471                i = 2
24472                if i == 2:
24473                    try:
24474                        i = 3
24475                    except:ˇ
24476    "});
24477
24478    // test `except` outdents to outer "try" block
24479    cx.set_state(indoc! {"
24480        def main():
24481            try:
24482                i = 2
24483                if i == 2:
24484                    try:
24485                        i = 3
24486                ˇ
24487    "});
24488    cx.update_editor(|editor, window, cx| {
24489        editor.handle_input("except:", window, cx);
24490    });
24491    cx.assert_editor_state(indoc! {"
24492        def main():
24493            try:
24494                i = 2
24495                if i == 2:
24496                    try:
24497                        i = 3
24498            except:ˇ
24499    "});
24500
24501    // test `else` stays at correct indent when typed after `for` block
24502    cx.set_state(indoc! {"
24503        def main():
24504            for i in range(10):
24505                if i == 3:
24506                    break
24507            ˇ
24508    "});
24509    cx.update_editor(|editor, window, cx| {
24510        editor.handle_input("else:", window, cx);
24511    });
24512    cx.assert_editor_state(indoc! {"
24513        def main():
24514            for i in range(10):
24515                if i == 3:
24516                    break
24517            else:ˇ
24518    "});
24519
24520    // test does not outdent on typing after line with square brackets
24521    cx.set_state(indoc! {"
24522        def f() -> list[str]:
24523            ˇ
24524    "});
24525    cx.update_editor(|editor, window, cx| {
24526        editor.handle_input("a", window, cx);
24527    });
24528    cx.assert_editor_state(indoc! {"
24529        def f() -> list[str]:
2453024531    "});
24532
24533    // test does not outdent on typing : after case keyword
24534    cx.set_state(indoc! {"
24535        match 1:
24536            caseˇ
24537    "});
24538    cx.update_editor(|editor, window, cx| {
24539        editor.handle_input(":", window, cx);
24540    });
24541    cx.assert_editor_state(indoc! {"
24542        match 1:
24543            case:ˇ
24544    "});
24545}
24546
24547#[gpui::test]
24548async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24549    init_test(cx, |_| {});
24550    update_test_language_settings(cx, |settings| {
24551        settings.defaults.extend_comment_on_newline = Some(false);
24552    });
24553    let mut cx = EditorTestContext::new(cx).await;
24554    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24555    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24556
24557    // test correct indent after newline on comment
24558    cx.set_state(indoc! {"
24559        # COMMENT:ˇ
24560    "});
24561    cx.update_editor(|editor, window, cx| {
24562        editor.newline(&Newline, window, cx);
24563    });
24564    cx.assert_editor_state(indoc! {"
24565        # COMMENT:
24566        ˇ
24567    "});
24568
24569    // test correct indent after newline in brackets
24570    cx.set_state(indoc! {"
24571        {ˇ}
24572    "});
24573    cx.update_editor(|editor, window, cx| {
24574        editor.newline(&Newline, window, cx);
24575    });
24576    cx.run_until_parked();
24577    cx.assert_editor_state(indoc! {"
24578        {
24579            ˇ
24580        }
24581    "});
24582
24583    cx.set_state(indoc! {"
24584        (ˇ)
24585    "});
24586    cx.update_editor(|editor, window, cx| {
24587        editor.newline(&Newline, window, cx);
24588    });
24589    cx.run_until_parked();
24590    cx.assert_editor_state(indoc! {"
24591        (
24592            ˇ
24593        )
24594    "});
24595
24596    // do not indent after empty lists or dictionaries
24597    cx.set_state(indoc! {"
24598        a = []ˇ
24599    "});
24600    cx.update_editor(|editor, window, cx| {
24601        editor.newline(&Newline, window, cx);
24602    });
24603    cx.run_until_parked();
24604    cx.assert_editor_state(indoc! {"
24605        a = []
24606        ˇ
24607    "});
24608}
24609
24610#[gpui::test]
24611async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24612    init_test(cx, |_| {});
24613
24614    let mut cx = EditorTestContext::new(cx).await;
24615    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24616    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24617
24618    // test cursor move to start of each line on tab
24619    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24620    cx.set_state(indoc! {"
24621        function main() {
24622        ˇ    for item in $items; do
24623        ˇ        while [ -n \"$item\" ]; do
24624        ˇ            if [ \"$value\" -gt 10 ]; then
24625        ˇ                continue
24626        ˇ            elif [ \"$value\" -lt 0 ]; then
24627        ˇ                break
24628        ˇ            else
24629        ˇ                echo \"$item\"
24630        ˇ            fi
24631        ˇ        done
24632        ˇ    done
24633        ˇ}
24634    "});
24635    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24636    cx.assert_editor_state(indoc! {"
24637        function main() {
24638            ˇfor item in $items; do
24639                ˇwhile [ -n \"$item\" ]; do
24640                    ˇif [ \"$value\" -gt 10 ]; then
24641                        ˇcontinue
24642                    ˇelif [ \"$value\" -lt 0 ]; then
24643                        ˇbreak
24644                    ˇelse
24645                        ˇecho \"$item\"
24646                    ˇfi
24647                ˇdone
24648            ˇdone
24649        ˇ}
24650    "});
24651    // test relative indent is preserved when tab
24652    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24653    cx.assert_editor_state(indoc! {"
24654        function main() {
24655                ˇfor item in $items; do
24656                    ˇwhile [ -n \"$item\" ]; do
24657                        ˇif [ \"$value\" -gt 10 ]; then
24658                            ˇcontinue
24659                        ˇelif [ \"$value\" -lt 0 ]; then
24660                            ˇbreak
24661                        ˇelse
24662                            ˇecho \"$item\"
24663                        ˇfi
24664                    ˇdone
24665                ˇdone
24666            ˇ}
24667    "});
24668
24669    // test cursor move to start of each line on tab
24670    // for `case` statement with patterns
24671    cx.set_state(indoc! {"
24672        function handle() {
24673        ˇ    case \"$1\" in
24674        ˇ        start)
24675        ˇ            echo \"a\"
24676        ˇ            ;;
24677        ˇ        stop)
24678        ˇ            echo \"b\"
24679        ˇ            ;;
24680        ˇ        *)
24681        ˇ            echo \"c\"
24682        ˇ            ;;
24683        ˇ    esac
24684        ˇ}
24685    "});
24686    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24687    cx.assert_editor_state(indoc! {"
24688        function handle() {
24689            ˇcase \"$1\" in
24690                ˇstart)
24691                    ˇecho \"a\"
24692                    ˇ;;
24693                ˇstop)
24694                    ˇecho \"b\"
24695                    ˇ;;
24696                ˇ*)
24697                    ˇecho \"c\"
24698                    ˇ;;
24699            ˇesac
24700        ˇ}
24701    "});
24702}
24703
24704#[gpui::test]
24705async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24706    init_test(cx, |_| {});
24707
24708    let mut cx = EditorTestContext::new(cx).await;
24709    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24710    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24711
24712    // test indents on comment insert
24713    cx.set_state(indoc! {"
24714        function main() {
24715        ˇ    for item in $items; do
24716        ˇ        while [ -n \"$item\" ]; do
24717        ˇ            if [ \"$value\" -gt 10 ]; then
24718        ˇ                continue
24719        ˇ            elif [ \"$value\" -lt 0 ]; then
24720        ˇ                break
24721        ˇ            else
24722        ˇ                echo \"$item\"
24723        ˇ            fi
24724        ˇ        done
24725        ˇ    done
24726        ˇ}
24727    "});
24728    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24729    cx.assert_editor_state(indoc! {"
24730        function main() {
24731        #ˇ    for item in $items; do
24732        #ˇ        while [ -n \"$item\" ]; do
24733        #ˇ            if [ \"$value\" -gt 10 ]; then
24734        #ˇ                continue
24735        #ˇ            elif [ \"$value\" -lt 0 ]; then
24736        #ˇ                break
24737        #ˇ            else
24738        #ˇ                echo \"$item\"
24739        #ˇ            fi
24740        #ˇ        done
24741        #ˇ    done
24742        #ˇ}
24743    "});
24744}
24745
24746#[gpui::test]
24747async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24748    init_test(cx, |_| {});
24749
24750    let mut cx = EditorTestContext::new(cx).await;
24751    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24752    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24753
24754    // test `else` auto outdents when typed inside `if` block
24755    cx.set_state(indoc! {"
24756        if [ \"$1\" = \"test\" ]; then
24757            echo \"foo bar\"
24758            ˇ
24759    "});
24760    cx.update_editor(|editor, window, cx| {
24761        editor.handle_input("else", window, cx);
24762    });
24763    cx.assert_editor_state(indoc! {"
24764        if [ \"$1\" = \"test\" ]; then
24765            echo \"foo bar\"
24766        elseˇ
24767    "});
24768
24769    // test `elif` auto outdents when typed inside `if` block
24770    cx.set_state(indoc! {"
24771        if [ \"$1\" = \"test\" ]; then
24772            echo \"foo bar\"
24773            ˇ
24774    "});
24775    cx.update_editor(|editor, window, cx| {
24776        editor.handle_input("elif", window, cx);
24777    });
24778    cx.assert_editor_state(indoc! {"
24779        if [ \"$1\" = \"test\" ]; then
24780            echo \"foo bar\"
24781        elifˇ
24782    "});
24783
24784    // test `fi` auto outdents when typed inside `else` block
24785    cx.set_state(indoc! {"
24786        if [ \"$1\" = \"test\" ]; then
24787            echo \"foo bar\"
24788        else
24789            echo \"bar baz\"
24790            ˇ
24791    "});
24792    cx.update_editor(|editor, window, cx| {
24793        editor.handle_input("fi", window, cx);
24794    });
24795    cx.assert_editor_state(indoc! {"
24796        if [ \"$1\" = \"test\" ]; then
24797            echo \"foo bar\"
24798        else
24799            echo \"bar baz\"
24800        fiˇ
24801    "});
24802
24803    // test `done` auto outdents when typed inside `while` block
24804    cx.set_state(indoc! {"
24805        while read line; do
24806            echo \"$line\"
24807            ˇ
24808    "});
24809    cx.update_editor(|editor, window, cx| {
24810        editor.handle_input("done", window, cx);
24811    });
24812    cx.assert_editor_state(indoc! {"
24813        while read line; do
24814            echo \"$line\"
24815        doneˇ
24816    "});
24817
24818    // test `done` auto outdents when typed inside `for` block
24819    cx.set_state(indoc! {"
24820        for file in *.txt; do
24821            cat \"$file\"
24822            ˇ
24823    "});
24824    cx.update_editor(|editor, window, cx| {
24825        editor.handle_input("done", window, cx);
24826    });
24827    cx.assert_editor_state(indoc! {"
24828        for file in *.txt; do
24829            cat \"$file\"
24830        doneˇ
24831    "});
24832
24833    // test `esac` auto outdents when typed inside `case` block
24834    cx.set_state(indoc! {"
24835        case \"$1\" in
24836            start)
24837                echo \"foo bar\"
24838                ;;
24839            stop)
24840                echo \"bar baz\"
24841                ;;
24842            ˇ
24843    "});
24844    cx.update_editor(|editor, window, cx| {
24845        editor.handle_input("esac", window, cx);
24846    });
24847    cx.assert_editor_state(indoc! {"
24848        case \"$1\" in
24849            start)
24850                echo \"foo bar\"
24851                ;;
24852            stop)
24853                echo \"bar baz\"
24854                ;;
24855        esacˇ
24856    "});
24857
24858    // test `*)` auto outdents when typed inside `case` block
24859    cx.set_state(indoc! {"
24860        case \"$1\" in
24861            start)
24862                echo \"foo bar\"
24863                ;;
24864                ˇ
24865    "});
24866    cx.update_editor(|editor, window, cx| {
24867        editor.handle_input("*)", window, cx);
24868    });
24869    cx.assert_editor_state(indoc! {"
24870        case \"$1\" in
24871            start)
24872                echo \"foo bar\"
24873                ;;
24874            *)ˇ
24875    "});
24876
24877    // test `fi` outdents to correct level with nested if blocks
24878    cx.set_state(indoc! {"
24879        if [ \"$1\" = \"test\" ]; then
24880            echo \"outer if\"
24881            if [ \"$2\" = \"debug\" ]; then
24882                echo \"inner if\"
24883                ˇ
24884    "});
24885    cx.update_editor(|editor, window, cx| {
24886        editor.handle_input("fi", window, cx);
24887    });
24888    cx.assert_editor_state(indoc! {"
24889        if [ \"$1\" = \"test\" ]; then
24890            echo \"outer if\"
24891            if [ \"$2\" = \"debug\" ]; then
24892                echo \"inner if\"
24893            fiˇ
24894    "});
24895}
24896
24897#[gpui::test]
24898async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
24899    init_test(cx, |_| {});
24900    update_test_language_settings(cx, |settings| {
24901        settings.defaults.extend_comment_on_newline = Some(false);
24902    });
24903    let mut cx = EditorTestContext::new(cx).await;
24904    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24905    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24906
24907    // test correct indent after newline on comment
24908    cx.set_state(indoc! {"
24909        # COMMENT:ˇ
24910    "});
24911    cx.update_editor(|editor, window, cx| {
24912        editor.newline(&Newline, window, cx);
24913    });
24914    cx.assert_editor_state(indoc! {"
24915        # COMMENT:
24916        ˇ
24917    "});
24918
24919    // test correct indent after newline after `then`
24920    cx.set_state(indoc! {"
24921
24922        if [ \"$1\" = \"test\" ]; thenˇ
24923    "});
24924    cx.update_editor(|editor, window, cx| {
24925        editor.newline(&Newline, window, cx);
24926    });
24927    cx.run_until_parked();
24928    cx.assert_editor_state(indoc! {"
24929
24930        if [ \"$1\" = \"test\" ]; then
24931            ˇ
24932    "});
24933
24934    // test correct indent after newline after `else`
24935    cx.set_state(indoc! {"
24936        if [ \"$1\" = \"test\" ]; then
24937        elseˇ
24938    "});
24939    cx.update_editor(|editor, window, cx| {
24940        editor.newline(&Newline, window, cx);
24941    });
24942    cx.run_until_parked();
24943    cx.assert_editor_state(indoc! {"
24944        if [ \"$1\" = \"test\" ]; then
24945        else
24946            ˇ
24947    "});
24948
24949    // test correct indent after newline after `elif`
24950    cx.set_state(indoc! {"
24951        if [ \"$1\" = \"test\" ]; then
24952        elifˇ
24953    "});
24954    cx.update_editor(|editor, window, cx| {
24955        editor.newline(&Newline, window, cx);
24956    });
24957    cx.run_until_parked();
24958    cx.assert_editor_state(indoc! {"
24959        if [ \"$1\" = \"test\" ]; then
24960        elif
24961            ˇ
24962    "});
24963
24964    // test correct indent after newline after `do`
24965    cx.set_state(indoc! {"
24966        for file in *.txt; doˇ
24967    "});
24968    cx.update_editor(|editor, window, cx| {
24969        editor.newline(&Newline, window, cx);
24970    });
24971    cx.run_until_parked();
24972    cx.assert_editor_state(indoc! {"
24973        for file in *.txt; do
24974            ˇ
24975    "});
24976
24977    // test correct indent after newline after case pattern
24978    cx.set_state(indoc! {"
24979        case \"$1\" in
24980            start)ˇ
24981    "});
24982    cx.update_editor(|editor, window, cx| {
24983        editor.newline(&Newline, window, cx);
24984    });
24985    cx.run_until_parked();
24986    cx.assert_editor_state(indoc! {"
24987        case \"$1\" in
24988            start)
24989                ˇ
24990    "});
24991
24992    // test correct indent after newline after case pattern
24993    cx.set_state(indoc! {"
24994        case \"$1\" in
24995            start)
24996                ;;
24997            *)ˇ
24998    "});
24999    cx.update_editor(|editor, window, cx| {
25000        editor.newline(&Newline, window, cx);
25001    });
25002    cx.run_until_parked();
25003    cx.assert_editor_state(indoc! {"
25004        case \"$1\" in
25005            start)
25006                ;;
25007            *)
25008                ˇ
25009    "});
25010
25011    // test correct indent after newline after function opening brace
25012    cx.set_state(indoc! {"
25013        function test() {ˇ}
25014    "});
25015    cx.update_editor(|editor, window, cx| {
25016        editor.newline(&Newline, window, cx);
25017    });
25018    cx.run_until_parked();
25019    cx.assert_editor_state(indoc! {"
25020        function test() {
25021            ˇ
25022        }
25023    "});
25024
25025    // test no extra indent after semicolon on same line
25026    cx.set_state(indoc! {"
25027        echo \"test\"25028    "});
25029    cx.update_editor(|editor, window, cx| {
25030        editor.newline(&Newline, window, cx);
25031    });
25032    cx.run_until_parked();
25033    cx.assert_editor_state(indoc! {"
25034        echo \"test\";
25035        ˇ
25036    "});
25037}
25038
25039fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25040    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25041    point..point
25042}
25043
25044#[track_caller]
25045fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25046    let (text, ranges) = marked_text_ranges(marked_text, true);
25047    assert_eq!(editor.text(cx), text);
25048    assert_eq!(
25049        editor.selections.ranges(cx),
25050        ranges,
25051        "Assert selections are {}",
25052        marked_text
25053    );
25054}
25055
25056pub fn handle_signature_help_request(
25057    cx: &mut EditorLspTestContext,
25058    mocked_response: lsp::SignatureHelp,
25059) -> impl Future<Output = ()> + use<> {
25060    let mut request =
25061        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25062            let mocked_response = mocked_response.clone();
25063            async move { Ok(Some(mocked_response)) }
25064        });
25065
25066    async move {
25067        request.next().await;
25068    }
25069}
25070
25071#[track_caller]
25072pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25073    cx.update_editor(|editor, _, _| {
25074        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25075            let entries = menu.entries.borrow();
25076            let entries = entries
25077                .iter()
25078                .map(|entry| entry.string.as_str())
25079                .collect::<Vec<_>>();
25080            assert_eq!(entries, expected);
25081        } else {
25082            panic!("Expected completions menu");
25083        }
25084    });
25085}
25086
25087/// Handle completion request passing a marked string specifying where the completion
25088/// should be triggered from using '|' character, what range should be replaced, and what completions
25089/// should be returned using '<' and '>' to delimit the range.
25090///
25091/// Also see `handle_completion_request_with_insert_and_replace`.
25092#[track_caller]
25093pub fn handle_completion_request(
25094    marked_string: &str,
25095    completions: Vec<&'static str>,
25096    is_incomplete: bool,
25097    counter: Arc<AtomicUsize>,
25098    cx: &mut EditorLspTestContext,
25099) -> impl Future<Output = ()> {
25100    let complete_from_marker: TextRangeMarker = '|'.into();
25101    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25102    let (_, mut marked_ranges) = marked_text_ranges_by(
25103        marked_string,
25104        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25105    );
25106
25107    let complete_from_position =
25108        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25109    let replace_range =
25110        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25111
25112    let mut request =
25113        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25114            let completions = completions.clone();
25115            counter.fetch_add(1, atomic::Ordering::Release);
25116            async move {
25117                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25118                assert_eq!(
25119                    params.text_document_position.position,
25120                    complete_from_position
25121                );
25122                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25123                    is_incomplete,
25124                    item_defaults: None,
25125                    items: completions
25126                        .iter()
25127                        .map(|completion_text| lsp::CompletionItem {
25128                            label: completion_text.to_string(),
25129                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25130                                range: replace_range,
25131                                new_text: completion_text.to_string(),
25132                            })),
25133                            ..Default::default()
25134                        })
25135                        .collect(),
25136                })))
25137            }
25138        });
25139
25140    async move {
25141        request.next().await;
25142    }
25143}
25144
25145/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25146/// given instead, which also contains an `insert` range.
25147///
25148/// This function uses markers to define ranges:
25149/// - `|` marks the cursor position
25150/// - `<>` marks the replace range
25151/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25152pub fn handle_completion_request_with_insert_and_replace(
25153    cx: &mut EditorLspTestContext,
25154    marked_string: &str,
25155    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25156    counter: Arc<AtomicUsize>,
25157) -> impl Future<Output = ()> {
25158    let complete_from_marker: TextRangeMarker = '|'.into();
25159    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25160    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25161
25162    let (_, mut marked_ranges) = marked_text_ranges_by(
25163        marked_string,
25164        vec![
25165            complete_from_marker.clone(),
25166            replace_range_marker.clone(),
25167            insert_range_marker.clone(),
25168        ],
25169    );
25170
25171    let complete_from_position =
25172        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25173    let replace_range =
25174        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25175
25176    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25177        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25178        _ => lsp::Range {
25179            start: replace_range.start,
25180            end: complete_from_position,
25181        },
25182    };
25183
25184    let mut request =
25185        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25186            let completions = completions.clone();
25187            counter.fetch_add(1, atomic::Ordering::Release);
25188            async move {
25189                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25190                assert_eq!(
25191                    params.text_document_position.position, complete_from_position,
25192                    "marker `|` position doesn't match",
25193                );
25194                Ok(Some(lsp::CompletionResponse::Array(
25195                    completions
25196                        .iter()
25197                        .map(|(label, new_text)| lsp::CompletionItem {
25198                            label: label.to_string(),
25199                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25200                                lsp::InsertReplaceEdit {
25201                                    insert: insert_range,
25202                                    replace: replace_range,
25203                                    new_text: new_text.to_string(),
25204                                },
25205                            )),
25206                            ..Default::default()
25207                        })
25208                        .collect(),
25209                )))
25210            }
25211        });
25212
25213    async move {
25214        request.next().await;
25215    }
25216}
25217
25218fn handle_resolve_completion_request(
25219    cx: &mut EditorLspTestContext,
25220    edits: Option<Vec<(&'static str, &'static str)>>,
25221) -> impl Future<Output = ()> {
25222    let edits = edits.map(|edits| {
25223        edits
25224            .iter()
25225            .map(|(marked_string, new_text)| {
25226                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25227                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25228                lsp::TextEdit::new(replace_range, new_text.to_string())
25229            })
25230            .collect::<Vec<_>>()
25231    });
25232
25233    let mut request =
25234        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25235            let edits = edits.clone();
25236            async move {
25237                Ok(lsp::CompletionItem {
25238                    additional_text_edits: edits,
25239                    ..Default::default()
25240                })
25241            }
25242        });
25243
25244    async move {
25245        request.next().await;
25246    }
25247}
25248
25249pub(crate) fn update_test_language_settings(
25250    cx: &mut TestAppContext,
25251    f: impl Fn(&mut AllLanguageSettingsContent),
25252) {
25253    cx.update(|cx| {
25254        SettingsStore::update_global(cx, |store, cx| {
25255            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25256        });
25257    });
25258}
25259
25260pub(crate) fn update_test_project_settings(
25261    cx: &mut TestAppContext,
25262    f: impl Fn(&mut ProjectSettingsContent),
25263) {
25264    cx.update(|cx| {
25265        SettingsStore::update_global(cx, |store, cx| {
25266            store.update_user_settings(cx, |settings| f(&mut settings.project));
25267        });
25268    });
25269}
25270
25271pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25272    cx.update(|cx| {
25273        assets::Assets.load_test_fonts(cx);
25274        let store = SettingsStore::test(cx);
25275        cx.set_global(store);
25276        theme::init(theme::LoadThemes::JustBase, cx);
25277        release_channel::init(SemanticVersion::default(), cx);
25278        client::init_settings(cx);
25279        language::init(cx);
25280        Project::init_settings(cx);
25281        workspace::init_settings(cx);
25282        crate::init(cx);
25283    });
25284    zlog::init_test();
25285    update_test_language_settings(cx, f);
25286}
25287
25288#[track_caller]
25289fn assert_hunk_revert(
25290    not_reverted_text_with_selections: &str,
25291    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25292    expected_reverted_text_with_selections: &str,
25293    base_text: &str,
25294    cx: &mut EditorLspTestContext,
25295) {
25296    cx.set_state(not_reverted_text_with_selections);
25297    cx.set_head_text(base_text);
25298    cx.executor().run_until_parked();
25299
25300    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25301        let snapshot = editor.snapshot(window, cx);
25302        let reverted_hunk_statuses = snapshot
25303            .buffer_snapshot
25304            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
25305            .map(|hunk| hunk.status().kind)
25306            .collect::<Vec<_>>();
25307
25308        editor.git_restore(&Default::default(), window, cx);
25309        reverted_hunk_statuses
25310    });
25311    cx.executor().run_until_parked();
25312    cx.assert_editor_state(expected_reverted_text_with_selections);
25313    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25314}
25315
25316#[gpui::test(iterations = 10)]
25317async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25318    init_test(cx, |_| {});
25319
25320    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25321    let counter = diagnostic_requests.clone();
25322
25323    let fs = FakeFs::new(cx.executor());
25324    fs.insert_tree(
25325        path!("/a"),
25326        json!({
25327            "first.rs": "fn main() { let a = 5; }",
25328            "second.rs": "// Test file",
25329        }),
25330    )
25331    .await;
25332
25333    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25334    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25335    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25336
25337    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25338    language_registry.add(rust_lang());
25339    let mut fake_servers = language_registry.register_fake_lsp(
25340        "Rust",
25341        FakeLspAdapter {
25342            capabilities: lsp::ServerCapabilities {
25343                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25344                    lsp::DiagnosticOptions {
25345                        identifier: None,
25346                        inter_file_dependencies: true,
25347                        workspace_diagnostics: true,
25348                        work_done_progress_options: Default::default(),
25349                    },
25350                )),
25351                ..Default::default()
25352            },
25353            ..Default::default()
25354        },
25355    );
25356
25357    let editor = workspace
25358        .update(cx, |workspace, window, cx| {
25359            workspace.open_abs_path(
25360                PathBuf::from(path!("/a/first.rs")),
25361                OpenOptions::default(),
25362                window,
25363                cx,
25364            )
25365        })
25366        .unwrap()
25367        .await
25368        .unwrap()
25369        .downcast::<Editor>()
25370        .unwrap();
25371    let fake_server = fake_servers.next().await.unwrap();
25372    let server_id = fake_server.server.server_id();
25373    let mut first_request = fake_server
25374        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25375            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25376            let result_id = Some(new_result_id.to_string());
25377            assert_eq!(
25378                params.text_document.uri,
25379                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25380            );
25381            async move {
25382                Ok(lsp::DocumentDiagnosticReportResult::Report(
25383                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25384                        related_documents: None,
25385                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25386                            items: Vec::new(),
25387                            result_id,
25388                        },
25389                    }),
25390                ))
25391            }
25392        });
25393
25394    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25395        project.update(cx, |project, cx| {
25396            let buffer_id = editor
25397                .read(cx)
25398                .buffer()
25399                .read(cx)
25400                .as_singleton()
25401                .expect("created a singleton buffer")
25402                .read(cx)
25403                .remote_id();
25404            let buffer_result_id = project
25405                .lsp_store()
25406                .read(cx)
25407                .result_id(server_id, buffer_id, cx);
25408            assert_eq!(expected, buffer_result_id);
25409        });
25410    };
25411
25412    ensure_result_id(None, cx);
25413    cx.executor().advance_clock(Duration::from_millis(60));
25414    cx.executor().run_until_parked();
25415    assert_eq!(
25416        diagnostic_requests.load(atomic::Ordering::Acquire),
25417        1,
25418        "Opening file should trigger diagnostic request"
25419    );
25420    first_request
25421        .next()
25422        .await
25423        .expect("should have sent the first diagnostics pull request");
25424    ensure_result_id(Some("1".to_string()), cx);
25425
25426    // Editing should trigger diagnostics
25427    editor.update_in(cx, |editor, window, cx| {
25428        editor.handle_input("2", window, cx)
25429    });
25430    cx.executor().advance_clock(Duration::from_millis(60));
25431    cx.executor().run_until_parked();
25432    assert_eq!(
25433        diagnostic_requests.load(atomic::Ordering::Acquire),
25434        2,
25435        "Editing should trigger diagnostic request"
25436    );
25437    ensure_result_id(Some("2".to_string()), cx);
25438
25439    // Moving cursor should not trigger diagnostic request
25440    editor.update_in(cx, |editor, window, cx| {
25441        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25442            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25443        });
25444    });
25445    cx.executor().advance_clock(Duration::from_millis(60));
25446    cx.executor().run_until_parked();
25447    assert_eq!(
25448        diagnostic_requests.load(atomic::Ordering::Acquire),
25449        2,
25450        "Cursor movement should not trigger diagnostic request"
25451    );
25452    ensure_result_id(Some("2".to_string()), cx);
25453    // Multiple rapid edits should be debounced
25454    for _ in 0..5 {
25455        editor.update_in(cx, |editor, window, cx| {
25456            editor.handle_input("x", window, cx)
25457        });
25458    }
25459    cx.executor().advance_clock(Duration::from_millis(60));
25460    cx.executor().run_until_parked();
25461
25462    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25463    assert!(
25464        final_requests <= 4,
25465        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25466    );
25467    ensure_result_id(Some(final_requests.to_string()), cx);
25468}
25469
25470#[gpui::test]
25471async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25472    // Regression test for issue #11671
25473    // Previously, adding a cursor after moving multiple cursors would reset
25474    // the cursor count instead of adding to the existing cursors.
25475    init_test(cx, |_| {});
25476    let mut cx = EditorTestContext::new(cx).await;
25477
25478    // Create a simple buffer with cursor at start
25479    cx.set_state(indoc! {"
25480        ˇaaaa
25481        bbbb
25482        cccc
25483        dddd
25484        eeee
25485        ffff
25486        gggg
25487        hhhh"});
25488
25489    // Add 2 cursors below (so we have 3 total)
25490    cx.update_editor(|editor, window, cx| {
25491        editor.add_selection_below(&Default::default(), window, cx);
25492        editor.add_selection_below(&Default::default(), window, cx);
25493    });
25494
25495    // Verify we have 3 cursors
25496    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25497    assert_eq!(
25498        initial_count, 3,
25499        "Should have 3 cursors after adding 2 below"
25500    );
25501
25502    // Move down one line
25503    cx.update_editor(|editor, window, cx| {
25504        editor.move_down(&MoveDown, window, cx);
25505    });
25506
25507    // Add another cursor below
25508    cx.update_editor(|editor, window, cx| {
25509        editor.add_selection_below(&Default::default(), window, cx);
25510    });
25511
25512    // Should now have 4 cursors (3 original + 1 new)
25513    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25514    assert_eq!(
25515        final_count, 4,
25516        "Should have 4 cursors after moving and adding another"
25517    );
25518}
25519
25520#[gpui::test(iterations = 10)]
25521async fn test_document_colors(cx: &mut TestAppContext) {
25522    let expected_color = Rgba {
25523        r: 0.33,
25524        g: 0.33,
25525        b: 0.33,
25526        a: 0.33,
25527    };
25528
25529    init_test(cx, |_| {});
25530
25531    let fs = FakeFs::new(cx.executor());
25532    fs.insert_tree(
25533        path!("/a"),
25534        json!({
25535            "first.rs": "fn main() { let a = 5; }",
25536        }),
25537    )
25538    .await;
25539
25540    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25541    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25542    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25543
25544    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25545    language_registry.add(rust_lang());
25546    let mut fake_servers = language_registry.register_fake_lsp(
25547        "Rust",
25548        FakeLspAdapter {
25549            capabilities: lsp::ServerCapabilities {
25550                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25551                ..lsp::ServerCapabilities::default()
25552            },
25553            name: "rust-analyzer",
25554            ..FakeLspAdapter::default()
25555        },
25556    );
25557    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25558        "Rust",
25559        FakeLspAdapter {
25560            capabilities: lsp::ServerCapabilities {
25561                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25562                ..lsp::ServerCapabilities::default()
25563            },
25564            name: "not-rust-analyzer",
25565            ..FakeLspAdapter::default()
25566        },
25567    );
25568
25569    let editor = workspace
25570        .update(cx, |workspace, window, cx| {
25571            workspace.open_abs_path(
25572                PathBuf::from(path!("/a/first.rs")),
25573                OpenOptions::default(),
25574                window,
25575                cx,
25576            )
25577        })
25578        .unwrap()
25579        .await
25580        .unwrap()
25581        .downcast::<Editor>()
25582        .unwrap();
25583    let fake_language_server = fake_servers.next().await.unwrap();
25584    let fake_language_server_without_capabilities =
25585        fake_servers_without_capabilities.next().await.unwrap();
25586    let requests_made = Arc::new(AtomicUsize::new(0));
25587    let closure_requests_made = Arc::clone(&requests_made);
25588    let mut color_request_handle = fake_language_server
25589        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25590            let requests_made = Arc::clone(&closure_requests_made);
25591            async move {
25592                assert_eq!(
25593                    params.text_document.uri,
25594                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25595                );
25596                requests_made.fetch_add(1, atomic::Ordering::Release);
25597                Ok(vec![
25598                    lsp::ColorInformation {
25599                        range: lsp::Range {
25600                            start: lsp::Position {
25601                                line: 0,
25602                                character: 0,
25603                            },
25604                            end: lsp::Position {
25605                                line: 0,
25606                                character: 1,
25607                            },
25608                        },
25609                        color: lsp::Color {
25610                            red: 0.33,
25611                            green: 0.33,
25612                            blue: 0.33,
25613                            alpha: 0.33,
25614                        },
25615                    },
25616                    lsp::ColorInformation {
25617                        range: lsp::Range {
25618                            start: lsp::Position {
25619                                line: 0,
25620                                character: 0,
25621                            },
25622                            end: lsp::Position {
25623                                line: 0,
25624                                character: 1,
25625                            },
25626                        },
25627                        color: lsp::Color {
25628                            red: 0.33,
25629                            green: 0.33,
25630                            blue: 0.33,
25631                            alpha: 0.33,
25632                        },
25633                    },
25634                ])
25635            }
25636        });
25637
25638    let _handle = fake_language_server_without_capabilities
25639        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25640            panic!("Should not be called");
25641        });
25642    cx.executor().advance_clock(Duration::from_millis(100));
25643    color_request_handle.next().await.unwrap();
25644    cx.run_until_parked();
25645    assert_eq!(
25646        1,
25647        requests_made.load(atomic::Ordering::Acquire),
25648        "Should query for colors once per editor open"
25649    );
25650    editor.update_in(cx, |editor, _, cx| {
25651        assert_eq!(
25652            vec![expected_color],
25653            extract_color_inlays(editor, cx),
25654            "Should have an initial inlay"
25655        );
25656    });
25657
25658    // opening another file in a split should not influence the LSP query counter
25659    workspace
25660        .update(cx, |workspace, window, cx| {
25661            assert_eq!(
25662                workspace.panes().len(),
25663                1,
25664                "Should have one pane with one editor"
25665            );
25666            workspace.move_item_to_pane_in_direction(
25667                &MoveItemToPaneInDirection {
25668                    direction: SplitDirection::Right,
25669                    focus: false,
25670                    clone: true,
25671                },
25672                window,
25673                cx,
25674            );
25675        })
25676        .unwrap();
25677    cx.run_until_parked();
25678    workspace
25679        .update(cx, |workspace, _, cx| {
25680            let panes = workspace.panes();
25681            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25682            for pane in panes {
25683                let editor = pane
25684                    .read(cx)
25685                    .active_item()
25686                    .and_then(|item| item.downcast::<Editor>())
25687                    .expect("Should have opened an editor in each split");
25688                let editor_file = editor
25689                    .read(cx)
25690                    .buffer()
25691                    .read(cx)
25692                    .as_singleton()
25693                    .expect("test deals with singleton buffers")
25694                    .read(cx)
25695                    .file()
25696                    .expect("test buffese should have a file")
25697                    .path();
25698                assert_eq!(
25699                    editor_file.as_ref(),
25700                    Path::new("first.rs"),
25701                    "Both editors should be opened for the same file"
25702                )
25703            }
25704        })
25705        .unwrap();
25706
25707    cx.executor().advance_clock(Duration::from_millis(500));
25708    let save = editor.update_in(cx, |editor, window, cx| {
25709        editor.move_to_end(&MoveToEnd, window, cx);
25710        editor.handle_input("dirty", window, cx);
25711        editor.save(
25712            SaveOptions {
25713                format: true,
25714                autosave: true,
25715            },
25716            project.clone(),
25717            window,
25718            cx,
25719        )
25720    });
25721    save.await.unwrap();
25722
25723    color_request_handle.next().await.unwrap();
25724    cx.run_until_parked();
25725    assert_eq!(
25726        3,
25727        requests_made.load(atomic::Ordering::Acquire),
25728        "Should query for colors once per save and once per formatting after save"
25729    );
25730
25731    drop(editor);
25732    let close = workspace
25733        .update(cx, |workspace, window, cx| {
25734            workspace.active_pane().update(cx, |pane, cx| {
25735                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25736            })
25737        })
25738        .unwrap();
25739    close.await.unwrap();
25740    let close = workspace
25741        .update(cx, |workspace, window, cx| {
25742            workspace.active_pane().update(cx, |pane, cx| {
25743                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25744            })
25745        })
25746        .unwrap();
25747    close.await.unwrap();
25748    assert_eq!(
25749        3,
25750        requests_made.load(atomic::Ordering::Acquire),
25751        "After saving and closing all editors, no extra requests should be made"
25752    );
25753    workspace
25754        .update(cx, |workspace, _, cx| {
25755            assert!(
25756                workspace.active_item(cx).is_none(),
25757                "Should close all editors"
25758            )
25759        })
25760        .unwrap();
25761
25762    workspace
25763        .update(cx, |workspace, window, cx| {
25764            workspace.active_pane().update(cx, |pane, cx| {
25765                pane.navigate_backward(&Default::default(), window, cx);
25766            })
25767        })
25768        .unwrap();
25769    cx.executor().advance_clock(Duration::from_millis(100));
25770    cx.run_until_parked();
25771    let editor = workspace
25772        .update(cx, |workspace, _, cx| {
25773            workspace
25774                .active_item(cx)
25775                .expect("Should have reopened the editor again after navigating back")
25776                .downcast::<Editor>()
25777                .expect("Should be an editor")
25778        })
25779        .unwrap();
25780    color_request_handle.next().await.unwrap();
25781    assert_eq!(
25782        3,
25783        requests_made.load(atomic::Ordering::Acquire),
25784        "Cache should be reused on buffer close and reopen"
25785    );
25786    editor.update(cx, |editor, cx| {
25787        assert_eq!(
25788            vec![expected_color],
25789            extract_color_inlays(editor, cx),
25790            "Should have an initial inlay"
25791        );
25792    });
25793}
25794
25795#[gpui::test]
25796async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
25797    init_test(cx, |_| {});
25798    let (editor, cx) = cx.add_window_view(Editor::single_line);
25799    editor.update_in(cx, |editor, window, cx| {
25800        editor.set_text("oops\n\nwow\n", window, cx)
25801    });
25802    cx.run_until_parked();
25803    editor.update(cx, |editor, cx| {
25804        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
25805    });
25806    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
25807    cx.run_until_parked();
25808    editor.update(cx, |editor, cx| {
25809        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
25810    });
25811}
25812
25813#[gpui::test]
25814async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
25815    init_test(cx, |_| {});
25816
25817    cx.update(|cx| {
25818        register_project_item::<Editor>(cx);
25819    });
25820
25821    let fs = FakeFs::new(cx.executor());
25822    fs.insert_tree("/root1", json!({})).await;
25823    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
25824        .await;
25825
25826    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
25827    let (workspace, cx) =
25828        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25829
25830    let worktree_id = project.update(cx, |project, cx| {
25831        project.worktrees(cx).next().unwrap().read(cx).id()
25832    });
25833
25834    let handle = workspace
25835        .update_in(cx, |workspace, window, cx| {
25836            let project_path = (worktree_id, "one.pdf");
25837            workspace.open_path(project_path, None, true, window, cx)
25838        })
25839        .await
25840        .unwrap();
25841
25842    assert_eq!(
25843        handle.to_any().entity_type(),
25844        TypeId::of::<InvalidBufferView>()
25845    );
25846}
25847
25848#[gpui::test]
25849async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
25850    init_test(cx, |_| {});
25851
25852    let language = Arc::new(Language::new(
25853        LanguageConfig::default(),
25854        Some(tree_sitter_rust::LANGUAGE.into()),
25855    ));
25856
25857    // Test hierarchical sibling navigation
25858    let text = r#"
25859        fn outer() {
25860            if condition {
25861                let a = 1;
25862            }
25863            let b = 2;
25864        }
25865
25866        fn another() {
25867            let c = 3;
25868        }
25869    "#;
25870
25871    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
25872    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25873    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
25874
25875    // Wait for parsing to complete
25876    editor
25877        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
25878        .await;
25879
25880    editor.update_in(cx, |editor, window, cx| {
25881        // Start by selecting "let a = 1;" inside the if block
25882        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25883            s.select_display_ranges([
25884                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
25885            ]);
25886        });
25887
25888        let initial_selection = editor.selections.display_ranges(cx);
25889        assert_eq!(initial_selection.len(), 1, "Should have one selection");
25890
25891        // Test select next sibling - should move up levels to find the next sibling
25892        // Since "let a = 1;" has no siblings in the if block, it should move up
25893        // to find "let b = 2;" which is a sibling of the if block
25894        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25895        let next_selection = editor.selections.display_ranges(cx);
25896
25897        // Should have a selection and it should be different from the initial
25898        assert_eq!(
25899            next_selection.len(),
25900            1,
25901            "Should have one selection after next"
25902        );
25903        assert_ne!(
25904            next_selection[0], initial_selection[0],
25905            "Next sibling selection should be different"
25906        );
25907
25908        // Test hierarchical navigation by going to the end of the current function
25909        // and trying to navigate to the next function
25910        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25911            s.select_display_ranges([
25912                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
25913            ]);
25914        });
25915
25916        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25917        let function_next_selection = editor.selections.display_ranges(cx);
25918
25919        // Should move to the next function
25920        assert_eq!(
25921            function_next_selection.len(),
25922            1,
25923            "Should have one selection after function next"
25924        );
25925
25926        // Test select previous sibling navigation
25927        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
25928        let prev_selection = editor.selections.display_ranges(cx);
25929
25930        // Should have a selection and it should be different
25931        assert_eq!(
25932            prev_selection.len(),
25933            1,
25934            "Should have one selection after prev"
25935        );
25936        assert_ne!(
25937            prev_selection[0], function_next_selection[0],
25938            "Previous sibling selection should be different from next"
25939        );
25940    });
25941}
25942
25943#[gpui::test]
25944async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
25945    init_test(cx, |_| {});
25946
25947    let mut cx = EditorTestContext::new(cx).await;
25948    cx.set_state(
25949        "let ˇvariable = 42;
25950let another = variable + 1;
25951let result = variable * 2;",
25952    );
25953
25954    // Set up document highlights manually (simulating LSP response)
25955    cx.update_editor(|editor, _window, cx| {
25956        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
25957
25958        // Create highlights for "variable" occurrences
25959        let highlight_ranges = [
25960            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
25961            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
25962            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
25963        ];
25964
25965        let anchor_ranges: Vec<_> = highlight_ranges
25966            .iter()
25967            .map(|range| range.clone().to_anchors(&buffer_snapshot))
25968            .collect();
25969
25970        editor.highlight_background::<DocumentHighlightRead>(
25971            &anchor_ranges,
25972            |theme| theme.colors().editor_document_highlight_read_background,
25973            cx,
25974        );
25975    });
25976
25977    // Go to next highlight - should move to second "variable"
25978    cx.update_editor(|editor, window, cx| {
25979        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25980    });
25981    cx.assert_editor_state(
25982        "let variable = 42;
25983let another = ˇvariable + 1;
25984let result = variable * 2;",
25985    );
25986
25987    // Go to next highlight - should move to third "variable"
25988    cx.update_editor(|editor, window, cx| {
25989        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25990    });
25991    cx.assert_editor_state(
25992        "let variable = 42;
25993let another = variable + 1;
25994let result = ˇvariable * 2;",
25995    );
25996
25997    // Go to next highlight - should stay at third "variable" (no wrap-around)
25998    cx.update_editor(|editor, window, cx| {
25999        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26000    });
26001    cx.assert_editor_state(
26002        "let variable = 42;
26003let another = variable + 1;
26004let result = ˇvariable * 2;",
26005    );
26006
26007    // Now test going backwards from third position
26008    cx.update_editor(|editor, window, cx| {
26009        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26010    });
26011    cx.assert_editor_state(
26012        "let variable = 42;
26013let another = ˇvariable + 1;
26014let result = variable * 2;",
26015    );
26016
26017    // Go to previous highlight - should move to first "variable"
26018    cx.update_editor(|editor, window, cx| {
26019        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26020    });
26021    cx.assert_editor_state(
26022        "let ˇvariable = 42;
26023let another = variable + 1;
26024let result = variable * 2;",
26025    );
26026
26027    // Go to previous highlight - should stay on first "variable"
26028    cx.update_editor(|editor, window, cx| {
26029        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26030    });
26031    cx.assert_editor_state(
26032        "let ˇvariable = 42;
26033let another = variable + 1;
26034let result = variable * 2;",
26035    );
26036}
26037
26038#[gpui::test]
26039async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26040    cx: &mut gpui::TestAppContext,
26041) {
26042    init_test(cx, |_| {});
26043
26044    let url = "https://zed.dev";
26045
26046    let markdown_language = Arc::new(Language::new(
26047        LanguageConfig {
26048            name: "Markdown".into(),
26049            ..LanguageConfig::default()
26050        },
26051        None,
26052    ));
26053
26054    let mut cx = EditorTestContext::new(cx).await;
26055    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26056    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26057
26058    cx.update_editor(|editor, window, cx| {
26059        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26060        editor.paste(&Paste, window, cx);
26061    });
26062
26063    cx.assert_editor_state(&format!(
26064        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26065    ));
26066}
26067
26068#[gpui::test]
26069async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26070    cx: &mut gpui::TestAppContext,
26071) {
26072    init_test(cx, |_| {});
26073
26074    let url = "https://zed.dev";
26075
26076    let markdown_language = Arc::new(Language::new(
26077        LanguageConfig {
26078            name: "Markdown".into(),
26079            ..LanguageConfig::default()
26080        },
26081        None,
26082    ));
26083
26084    let mut cx = EditorTestContext::new(cx).await;
26085    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26086    cx.set_state(&format!(
26087        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26088    ));
26089
26090    cx.update_editor(|editor, window, cx| {
26091        editor.copy(&Copy, window, cx);
26092    });
26093
26094    cx.set_state(&format!(
26095        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26096    ));
26097
26098    cx.update_editor(|editor, window, cx| {
26099        editor.paste(&Paste, window, cx);
26100    });
26101
26102    cx.assert_editor_state(&format!(
26103        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26104    ));
26105}
26106
26107#[gpui::test]
26108async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26109    cx: &mut gpui::TestAppContext,
26110) {
26111    init_test(cx, |_| {});
26112
26113    let url = "https://zed.dev";
26114
26115    let markdown_language = Arc::new(Language::new(
26116        LanguageConfig {
26117            name: "Markdown".into(),
26118            ..LanguageConfig::default()
26119        },
26120        None,
26121    ));
26122
26123    let mut cx = EditorTestContext::new(cx).await;
26124    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26125    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26126
26127    cx.update_editor(|editor, window, cx| {
26128        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26129        editor.paste(&Paste, window, cx);
26130    });
26131
26132    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26133}
26134
26135#[gpui::test]
26136async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26137    cx: &mut gpui::TestAppContext,
26138) {
26139    init_test(cx, |_| {});
26140
26141    let text = "Awesome";
26142
26143    let markdown_language = Arc::new(Language::new(
26144        LanguageConfig {
26145            name: "Markdown".into(),
26146            ..LanguageConfig::default()
26147        },
26148        None,
26149    ));
26150
26151    let mut cx = EditorTestContext::new(cx).await;
26152    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26153    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26154
26155    cx.update_editor(|editor, window, cx| {
26156        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26157        editor.paste(&Paste, window, cx);
26158    });
26159
26160    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26161}
26162
26163#[gpui::test]
26164async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26165    cx: &mut gpui::TestAppContext,
26166) {
26167    init_test(cx, |_| {});
26168
26169    let url = "https://zed.dev";
26170
26171    let markdown_language = Arc::new(Language::new(
26172        LanguageConfig {
26173            name: "Rust".into(),
26174            ..LanguageConfig::default()
26175        },
26176        None,
26177    ));
26178
26179    let mut cx = EditorTestContext::new(cx).await;
26180    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26181    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26182
26183    cx.update_editor(|editor, window, cx| {
26184        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26185        editor.paste(&Paste, window, cx);
26186    });
26187
26188    cx.assert_editor_state(&format!(
26189        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26190    ));
26191}
26192
26193#[gpui::test]
26194async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26195    cx: &mut TestAppContext,
26196) {
26197    init_test(cx, |_| {});
26198
26199    let url = "https://zed.dev";
26200
26201    let markdown_language = Arc::new(Language::new(
26202        LanguageConfig {
26203            name: "Markdown".into(),
26204            ..LanguageConfig::default()
26205        },
26206        None,
26207    ));
26208
26209    let (editor, cx) = cx.add_window_view(|window, cx| {
26210        let multi_buffer = MultiBuffer::build_multi(
26211            [
26212                ("this will embed -> link", vec![Point::row_range(0..1)]),
26213                ("this will replace -> link", vec![Point::row_range(0..1)]),
26214            ],
26215            cx,
26216        );
26217        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26218        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26219            s.select_ranges(vec![
26220                Point::new(0, 19)..Point::new(0, 23),
26221                Point::new(1, 21)..Point::new(1, 25),
26222            ])
26223        });
26224        let first_buffer_id = multi_buffer
26225            .read(cx)
26226            .excerpt_buffer_ids()
26227            .into_iter()
26228            .next()
26229            .unwrap();
26230        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26231        first_buffer.update(cx, |buffer, cx| {
26232            buffer.set_language(Some(markdown_language.clone()), cx);
26233        });
26234
26235        editor
26236    });
26237    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26238
26239    cx.update_editor(|editor, window, cx| {
26240        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26241        editor.paste(&Paste, window, cx);
26242    });
26243
26244    cx.assert_editor_state(&format!(
26245        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26246    ));
26247}
26248
26249#[track_caller]
26250fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26251    editor
26252        .all_inlays(cx)
26253        .into_iter()
26254        .filter_map(|inlay| inlay.get_color())
26255        .map(Rgba::from)
26256        .collect()
26257}