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
 9157#[gpui::test]
 9158async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9159    init_test(cx, |_| {});
 9160
 9161    let base_text = r#"
 9162        impl A {
 9163            // this is an uncommitted comment
 9164
 9165            fn b() {
 9166                c();
 9167            }
 9168
 9169            // this is another uncommitted comment
 9170
 9171            fn d() {
 9172                // e
 9173                // f
 9174            }
 9175        }
 9176
 9177        fn g() {
 9178            // h
 9179        }
 9180    "#
 9181    .unindent();
 9182
 9183    let text = r#"
 9184        ˇimpl A {
 9185
 9186            fn b() {
 9187                c();
 9188            }
 9189
 9190            fn d() {
 9191                // e
 9192                // f
 9193            }
 9194        }
 9195
 9196        fn g() {
 9197            // h
 9198        }
 9199    "#
 9200    .unindent();
 9201
 9202    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9203    cx.set_state(&text);
 9204    cx.set_head_text(&base_text);
 9205    cx.update_editor(|editor, window, cx| {
 9206        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9207    });
 9208
 9209    cx.assert_state_with_diff(
 9210        "
 9211        ˇimpl A {
 9212      -     // this is an uncommitted comment
 9213
 9214            fn b() {
 9215                c();
 9216            }
 9217
 9218      -     // this is another uncommitted comment
 9219      -
 9220            fn d() {
 9221                // e
 9222                // f
 9223            }
 9224        }
 9225
 9226        fn g() {
 9227            // h
 9228        }
 9229    "
 9230        .unindent(),
 9231    );
 9232
 9233    let expected_display_text = "
 9234        impl A {
 9235            // this is an uncommitted comment
 9236
 9237            fn b() {
 9238 9239            }
 9240
 9241            // this is another uncommitted comment
 9242
 9243            fn d() {
 9244 9245            }
 9246        }
 9247
 9248        fn g() {
 9249 9250        }
 9251        "
 9252    .unindent();
 9253
 9254    cx.update_editor(|editor, window, cx| {
 9255        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9256        assert_eq!(editor.display_text(cx), expected_display_text);
 9257    });
 9258}
 9259
 9260#[gpui::test]
 9261async fn test_autoindent(cx: &mut TestAppContext) {
 9262    init_test(cx, |_| {});
 9263
 9264    let language = Arc::new(
 9265        Language::new(
 9266            LanguageConfig {
 9267                brackets: BracketPairConfig {
 9268                    pairs: vec![
 9269                        BracketPair {
 9270                            start: "{".to_string(),
 9271                            end: "}".to_string(),
 9272                            close: false,
 9273                            surround: false,
 9274                            newline: true,
 9275                        },
 9276                        BracketPair {
 9277                            start: "(".to_string(),
 9278                            end: ")".to_string(),
 9279                            close: false,
 9280                            surround: false,
 9281                            newline: true,
 9282                        },
 9283                    ],
 9284                    ..Default::default()
 9285                },
 9286                ..Default::default()
 9287            },
 9288            Some(tree_sitter_rust::LANGUAGE.into()),
 9289        )
 9290        .with_indents_query(
 9291            r#"
 9292                (_ "(" ")" @end) @indent
 9293                (_ "{" "}" @end) @indent
 9294            "#,
 9295        )
 9296        .unwrap(),
 9297    );
 9298
 9299    let text = "fn a() {}";
 9300
 9301    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9302    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9303    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9304    editor
 9305        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9306        .await;
 9307
 9308    editor.update_in(cx, |editor, window, cx| {
 9309        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9310            s.select_ranges([5..5, 8..8, 9..9])
 9311        });
 9312        editor.newline(&Newline, window, cx);
 9313        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9314        assert_eq!(
 9315            editor.selections.ranges(cx),
 9316            &[
 9317                Point::new(1, 4)..Point::new(1, 4),
 9318                Point::new(3, 4)..Point::new(3, 4),
 9319                Point::new(5, 0)..Point::new(5, 0)
 9320            ]
 9321        );
 9322    });
 9323}
 9324
 9325#[gpui::test]
 9326async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9327    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9328
 9329    let language = Arc::new(
 9330        Language::new(
 9331            LanguageConfig {
 9332                brackets: BracketPairConfig {
 9333                    pairs: vec![
 9334                        BracketPair {
 9335                            start: "{".to_string(),
 9336                            end: "}".to_string(),
 9337                            close: false,
 9338                            surround: false,
 9339                            newline: true,
 9340                        },
 9341                        BracketPair {
 9342                            start: "(".to_string(),
 9343                            end: ")".to_string(),
 9344                            close: false,
 9345                            surround: false,
 9346                            newline: true,
 9347                        },
 9348                    ],
 9349                    ..Default::default()
 9350                },
 9351                ..Default::default()
 9352            },
 9353            Some(tree_sitter_rust::LANGUAGE.into()),
 9354        )
 9355        .with_indents_query(
 9356            r#"
 9357                (_ "(" ")" @end) @indent
 9358                (_ "{" "}" @end) @indent
 9359            "#,
 9360        )
 9361        .unwrap(),
 9362    );
 9363
 9364    let text = "fn a() {}";
 9365
 9366    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9367    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9368    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9369    editor
 9370        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9371        .await;
 9372
 9373    editor.update_in(cx, |editor, window, cx| {
 9374        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9375            s.select_ranges([5..5, 8..8, 9..9])
 9376        });
 9377        editor.newline(&Newline, window, cx);
 9378        assert_eq!(
 9379            editor.text(cx),
 9380            indoc!(
 9381                "
 9382                fn a(
 9383
 9384                ) {
 9385
 9386                }
 9387                "
 9388            )
 9389        );
 9390        assert_eq!(
 9391            editor.selections.ranges(cx),
 9392            &[
 9393                Point::new(1, 0)..Point::new(1, 0),
 9394                Point::new(3, 0)..Point::new(3, 0),
 9395                Point::new(5, 0)..Point::new(5, 0)
 9396            ]
 9397        );
 9398    });
 9399}
 9400
 9401#[gpui::test]
 9402async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9403    init_test(cx, |settings| {
 9404        settings.defaults.auto_indent = Some(true);
 9405        settings.languages.0.insert(
 9406            "python".into(),
 9407            LanguageSettingsContent {
 9408                auto_indent: Some(false),
 9409                ..Default::default()
 9410            },
 9411        );
 9412    });
 9413
 9414    let mut cx = EditorTestContext::new(cx).await;
 9415
 9416    let injected_language = Arc::new(
 9417        Language::new(
 9418            LanguageConfig {
 9419                brackets: BracketPairConfig {
 9420                    pairs: vec![
 9421                        BracketPair {
 9422                            start: "{".to_string(),
 9423                            end: "}".to_string(),
 9424                            close: false,
 9425                            surround: false,
 9426                            newline: true,
 9427                        },
 9428                        BracketPair {
 9429                            start: "(".to_string(),
 9430                            end: ")".to_string(),
 9431                            close: true,
 9432                            surround: false,
 9433                            newline: true,
 9434                        },
 9435                    ],
 9436                    ..Default::default()
 9437                },
 9438                name: "python".into(),
 9439                ..Default::default()
 9440            },
 9441            Some(tree_sitter_python::LANGUAGE.into()),
 9442        )
 9443        .with_indents_query(
 9444            r#"
 9445                (_ "(" ")" @end) @indent
 9446                (_ "{" "}" @end) @indent
 9447            "#,
 9448        )
 9449        .unwrap(),
 9450    );
 9451
 9452    let language = Arc::new(
 9453        Language::new(
 9454            LanguageConfig {
 9455                brackets: BracketPairConfig {
 9456                    pairs: vec![
 9457                        BracketPair {
 9458                            start: "{".to_string(),
 9459                            end: "}".to_string(),
 9460                            close: false,
 9461                            surround: false,
 9462                            newline: true,
 9463                        },
 9464                        BracketPair {
 9465                            start: "(".to_string(),
 9466                            end: ")".to_string(),
 9467                            close: true,
 9468                            surround: false,
 9469                            newline: true,
 9470                        },
 9471                    ],
 9472                    ..Default::default()
 9473                },
 9474                name: LanguageName::new("rust"),
 9475                ..Default::default()
 9476            },
 9477            Some(tree_sitter_rust::LANGUAGE.into()),
 9478        )
 9479        .with_indents_query(
 9480            r#"
 9481                (_ "(" ")" @end) @indent
 9482                (_ "{" "}" @end) @indent
 9483            "#,
 9484        )
 9485        .unwrap()
 9486        .with_injection_query(
 9487            r#"
 9488            (macro_invocation
 9489                macro: (identifier) @_macro_name
 9490                (token_tree) @injection.content
 9491                (#set! injection.language "python"))
 9492           "#,
 9493        )
 9494        .unwrap(),
 9495    );
 9496
 9497    cx.language_registry().add(injected_language);
 9498    cx.language_registry().add(language.clone());
 9499
 9500    cx.update_buffer(|buffer, cx| {
 9501        buffer.set_language(Some(language), cx);
 9502    });
 9503
 9504    cx.set_state(r#"struct A {ˇ}"#);
 9505
 9506    cx.update_editor(|editor, window, cx| {
 9507        editor.newline(&Default::default(), window, cx);
 9508    });
 9509
 9510    cx.assert_editor_state(indoc!(
 9511        "struct A {
 9512            ˇ
 9513        }"
 9514    ));
 9515
 9516    cx.set_state(r#"select_biased!(ˇ)"#);
 9517
 9518    cx.update_editor(|editor, window, cx| {
 9519        editor.newline(&Default::default(), window, cx);
 9520        editor.handle_input("def ", window, cx);
 9521        editor.handle_input("(", window, cx);
 9522        editor.newline(&Default::default(), window, cx);
 9523        editor.handle_input("a", window, cx);
 9524    });
 9525
 9526    cx.assert_editor_state(indoc!(
 9527        "select_biased!(
 9528        def (
 9529 9530        )
 9531        )"
 9532    ));
 9533}
 9534
 9535#[gpui::test]
 9536async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9537    init_test(cx, |_| {});
 9538
 9539    {
 9540        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9541        cx.set_state(indoc! {"
 9542            impl A {
 9543
 9544                fn b() {}
 9545
 9546            «fn c() {
 9547
 9548            }ˇ»
 9549            }
 9550        "});
 9551
 9552        cx.update_editor(|editor, window, cx| {
 9553            editor.autoindent(&Default::default(), window, cx);
 9554        });
 9555
 9556        cx.assert_editor_state(indoc! {"
 9557            impl A {
 9558
 9559                fn b() {}
 9560
 9561                «fn c() {
 9562
 9563                }ˇ»
 9564            }
 9565        "});
 9566    }
 9567
 9568    {
 9569        let mut cx = EditorTestContext::new_multibuffer(
 9570            cx,
 9571            [indoc! { "
 9572                impl A {
 9573                «
 9574                // a
 9575                fn b(){}
 9576                »
 9577                «
 9578                    }
 9579                    fn c(){}
 9580                »
 9581            "}],
 9582        );
 9583
 9584        let buffer = cx.update_editor(|editor, _, cx| {
 9585            let buffer = editor.buffer().update(cx, |buffer, _| {
 9586                buffer.all_buffers().iter().next().unwrap().clone()
 9587            });
 9588            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9589            buffer
 9590        });
 9591
 9592        cx.run_until_parked();
 9593        cx.update_editor(|editor, window, cx| {
 9594            editor.select_all(&Default::default(), window, cx);
 9595            editor.autoindent(&Default::default(), window, cx)
 9596        });
 9597        cx.run_until_parked();
 9598
 9599        cx.update(|_, cx| {
 9600            assert_eq!(
 9601                buffer.read(cx).text(),
 9602                indoc! { "
 9603                    impl A {
 9604
 9605                        // a
 9606                        fn b(){}
 9607
 9608
 9609                    }
 9610                    fn c(){}
 9611
 9612                " }
 9613            )
 9614        });
 9615    }
 9616}
 9617
 9618#[gpui::test]
 9619async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9620    init_test(cx, |_| {});
 9621
 9622    let mut cx = EditorTestContext::new(cx).await;
 9623
 9624    let language = Arc::new(Language::new(
 9625        LanguageConfig {
 9626            brackets: BracketPairConfig {
 9627                pairs: vec![
 9628                    BracketPair {
 9629                        start: "{".to_string(),
 9630                        end: "}".to_string(),
 9631                        close: true,
 9632                        surround: true,
 9633                        newline: true,
 9634                    },
 9635                    BracketPair {
 9636                        start: "(".to_string(),
 9637                        end: ")".to_string(),
 9638                        close: true,
 9639                        surround: true,
 9640                        newline: true,
 9641                    },
 9642                    BracketPair {
 9643                        start: "/*".to_string(),
 9644                        end: " */".to_string(),
 9645                        close: true,
 9646                        surround: true,
 9647                        newline: true,
 9648                    },
 9649                    BracketPair {
 9650                        start: "[".to_string(),
 9651                        end: "]".to_string(),
 9652                        close: false,
 9653                        surround: false,
 9654                        newline: true,
 9655                    },
 9656                    BracketPair {
 9657                        start: "\"".to_string(),
 9658                        end: "\"".to_string(),
 9659                        close: true,
 9660                        surround: true,
 9661                        newline: false,
 9662                    },
 9663                    BracketPair {
 9664                        start: "<".to_string(),
 9665                        end: ">".to_string(),
 9666                        close: false,
 9667                        surround: true,
 9668                        newline: true,
 9669                    },
 9670                ],
 9671                ..Default::default()
 9672            },
 9673            autoclose_before: "})]".to_string(),
 9674            ..Default::default()
 9675        },
 9676        Some(tree_sitter_rust::LANGUAGE.into()),
 9677    ));
 9678
 9679    cx.language_registry().add(language.clone());
 9680    cx.update_buffer(|buffer, cx| {
 9681        buffer.set_language(Some(language), cx);
 9682    });
 9683
 9684    cx.set_state(
 9685        &r#"
 9686            🏀ˇ
 9687            εˇ
 9688            ❤️ˇ
 9689        "#
 9690        .unindent(),
 9691    );
 9692
 9693    // autoclose multiple nested brackets at multiple cursors
 9694    cx.update_editor(|editor, window, cx| {
 9695        editor.handle_input("{", window, cx);
 9696        editor.handle_input("{", window, cx);
 9697        editor.handle_input("{", window, cx);
 9698    });
 9699    cx.assert_editor_state(
 9700        &"
 9701            🏀{{{ˇ}}}
 9702            ε{{{ˇ}}}
 9703            ❤️{{{ˇ}}}
 9704        "
 9705        .unindent(),
 9706    );
 9707
 9708    // insert a different closing bracket
 9709    cx.update_editor(|editor, window, cx| {
 9710        editor.handle_input(")", window, cx);
 9711    });
 9712    cx.assert_editor_state(
 9713        &"
 9714            🏀{{{)ˇ}}}
 9715            ε{{{)ˇ}}}
 9716            ❤️{{{)ˇ}}}
 9717        "
 9718        .unindent(),
 9719    );
 9720
 9721    // skip over the auto-closed brackets when typing a closing bracket
 9722    cx.update_editor(|editor, window, cx| {
 9723        editor.move_right(&MoveRight, window, cx);
 9724        editor.handle_input("}", window, cx);
 9725        editor.handle_input("}", window, cx);
 9726        editor.handle_input("}", window, cx);
 9727    });
 9728    cx.assert_editor_state(
 9729        &"
 9730            🏀{{{)}}}}ˇ
 9731            ε{{{)}}}}ˇ
 9732            ❤️{{{)}}}}ˇ
 9733        "
 9734        .unindent(),
 9735    );
 9736
 9737    // autoclose multi-character pairs
 9738    cx.set_state(
 9739        &"
 9740            ˇ
 9741            ˇ
 9742        "
 9743        .unindent(),
 9744    );
 9745    cx.update_editor(|editor, window, cx| {
 9746        editor.handle_input("/", window, cx);
 9747        editor.handle_input("*", window, cx);
 9748    });
 9749    cx.assert_editor_state(
 9750        &"
 9751            /*ˇ */
 9752            /*ˇ */
 9753        "
 9754        .unindent(),
 9755    );
 9756
 9757    // one cursor autocloses a multi-character pair, one cursor
 9758    // does not autoclose.
 9759    cx.set_state(
 9760        &"
 9761 9762            ˇ
 9763        "
 9764        .unindent(),
 9765    );
 9766    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 9767    cx.assert_editor_state(
 9768        &"
 9769            /*ˇ */
 9770 9771        "
 9772        .unindent(),
 9773    );
 9774
 9775    // Don't autoclose if the next character isn't whitespace and isn't
 9776    // listed in the language's "autoclose_before" section.
 9777    cx.set_state("ˇa b");
 9778    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9779    cx.assert_editor_state("{ˇa b");
 9780
 9781    // Don't autoclose if `close` is false for the bracket pair
 9782    cx.set_state("ˇ");
 9783    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 9784    cx.assert_editor_state("");
 9785
 9786    // Surround with brackets if text is selected
 9787    cx.set_state("«aˇ» b");
 9788    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9789    cx.assert_editor_state("{«aˇ»} b");
 9790
 9791    // Autoclose when not immediately after a word character
 9792    cx.set_state("a ˇ");
 9793    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9794    cx.assert_editor_state("a \"ˇ\"");
 9795
 9796    // Autoclose pair where the start and end characters are the same
 9797    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9798    cx.assert_editor_state("a \"\"ˇ");
 9799
 9800    // Don't autoclose when immediately after a word character
 9801    cx.set_state("");
 9802    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9803    cx.assert_editor_state("a\"ˇ");
 9804
 9805    // Do autoclose when after a non-word character
 9806    cx.set_state("");
 9807    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9808    cx.assert_editor_state("{\"ˇ\"");
 9809
 9810    // Non identical pairs autoclose regardless of preceding character
 9811    cx.set_state("");
 9812    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9813    cx.assert_editor_state("a{ˇ}");
 9814
 9815    // Don't autoclose pair if autoclose is disabled
 9816    cx.set_state("ˇ");
 9817    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9818    cx.assert_editor_state("");
 9819
 9820    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 9821    cx.set_state("«aˇ» b");
 9822    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9823    cx.assert_editor_state("<«aˇ»> b");
 9824}
 9825
 9826#[gpui::test]
 9827async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 9828    init_test(cx, |settings| {
 9829        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9830    });
 9831
 9832    let mut cx = EditorTestContext::new(cx).await;
 9833
 9834    let language = Arc::new(Language::new(
 9835        LanguageConfig {
 9836            brackets: BracketPairConfig {
 9837                pairs: vec![
 9838                    BracketPair {
 9839                        start: "{".to_string(),
 9840                        end: "}".to_string(),
 9841                        close: true,
 9842                        surround: true,
 9843                        newline: true,
 9844                    },
 9845                    BracketPair {
 9846                        start: "(".to_string(),
 9847                        end: ")".to_string(),
 9848                        close: true,
 9849                        surround: true,
 9850                        newline: true,
 9851                    },
 9852                    BracketPair {
 9853                        start: "[".to_string(),
 9854                        end: "]".to_string(),
 9855                        close: false,
 9856                        surround: false,
 9857                        newline: true,
 9858                    },
 9859                ],
 9860                ..Default::default()
 9861            },
 9862            autoclose_before: "})]".to_string(),
 9863            ..Default::default()
 9864        },
 9865        Some(tree_sitter_rust::LANGUAGE.into()),
 9866    ));
 9867
 9868    cx.language_registry().add(language.clone());
 9869    cx.update_buffer(|buffer, cx| {
 9870        buffer.set_language(Some(language), cx);
 9871    });
 9872
 9873    cx.set_state(
 9874        &"
 9875            ˇ
 9876            ˇ
 9877            ˇ
 9878        "
 9879        .unindent(),
 9880    );
 9881
 9882    // ensure only matching closing brackets are skipped over
 9883    cx.update_editor(|editor, window, cx| {
 9884        editor.handle_input("}", window, cx);
 9885        editor.move_left(&MoveLeft, window, cx);
 9886        editor.handle_input(")", window, cx);
 9887        editor.move_left(&MoveLeft, window, cx);
 9888    });
 9889    cx.assert_editor_state(
 9890        &"
 9891            ˇ)}
 9892            ˇ)}
 9893            ˇ)}
 9894        "
 9895        .unindent(),
 9896    );
 9897
 9898    // skip-over closing brackets at multiple cursors
 9899    cx.update_editor(|editor, window, cx| {
 9900        editor.handle_input(")", window, cx);
 9901        editor.handle_input("}", window, cx);
 9902    });
 9903    cx.assert_editor_state(
 9904        &"
 9905            )}ˇ
 9906            )}ˇ
 9907            )}ˇ
 9908        "
 9909        .unindent(),
 9910    );
 9911
 9912    // ignore non-close brackets
 9913    cx.update_editor(|editor, window, cx| {
 9914        editor.handle_input("]", window, cx);
 9915        editor.move_left(&MoveLeft, window, cx);
 9916        editor.handle_input("]", window, cx);
 9917    });
 9918    cx.assert_editor_state(
 9919        &"
 9920            )}]ˇ]
 9921            )}]ˇ]
 9922            )}]ˇ]
 9923        "
 9924        .unindent(),
 9925    );
 9926}
 9927
 9928#[gpui::test]
 9929async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 9930    init_test(cx, |_| {});
 9931
 9932    let mut cx = EditorTestContext::new(cx).await;
 9933
 9934    let html_language = Arc::new(
 9935        Language::new(
 9936            LanguageConfig {
 9937                name: "HTML".into(),
 9938                brackets: BracketPairConfig {
 9939                    pairs: vec![
 9940                        BracketPair {
 9941                            start: "<".into(),
 9942                            end: ">".into(),
 9943                            close: true,
 9944                            ..Default::default()
 9945                        },
 9946                        BracketPair {
 9947                            start: "{".into(),
 9948                            end: "}".into(),
 9949                            close: true,
 9950                            ..Default::default()
 9951                        },
 9952                        BracketPair {
 9953                            start: "(".into(),
 9954                            end: ")".into(),
 9955                            close: true,
 9956                            ..Default::default()
 9957                        },
 9958                    ],
 9959                    ..Default::default()
 9960                },
 9961                autoclose_before: "})]>".into(),
 9962                ..Default::default()
 9963            },
 9964            Some(tree_sitter_html::LANGUAGE.into()),
 9965        )
 9966        .with_injection_query(
 9967            r#"
 9968            (script_element
 9969                (raw_text) @injection.content
 9970                (#set! injection.language "javascript"))
 9971            "#,
 9972        )
 9973        .unwrap(),
 9974    );
 9975
 9976    let javascript_language = Arc::new(Language::new(
 9977        LanguageConfig {
 9978            name: "JavaScript".into(),
 9979            brackets: BracketPairConfig {
 9980                pairs: vec![
 9981                    BracketPair {
 9982                        start: "/*".into(),
 9983                        end: " */".into(),
 9984                        close: true,
 9985                        ..Default::default()
 9986                    },
 9987                    BracketPair {
 9988                        start: "{".into(),
 9989                        end: "}".into(),
 9990                        close: true,
 9991                        ..Default::default()
 9992                    },
 9993                    BracketPair {
 9994                        start: "(".into(),
 9995                        end: ")".into(),
 9996                        close: true,
 9997                        ..Default::default()
 9998                    },
 9999                ],
10000                ..Default::default()
10001            },
10002            autoclose_before: "})]>".into(),
10003            ..Default::default()
10004        },
10005        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10006    ));
10007
10008    cx.language_registry().add(html_language.clone());
10009    cx.language_registry().add(javascript_language);
10010    cx.executor().run_until_parked();
10011
10012    cx.update_buffer(|buffer, cx| {
10013        buffer.set_language(Some(html_language), cx);
10014    });
10015
10016    cx.set_state(
10017        &r#"
10018            <body>ˇ
10019                <script>
10020                    var x = 1;ˇ
10021                </script>
10022            </body>ˇ
10023        "#
10024        .unindent(),
10025    );
10026
10027    // Precondition: different languages are active at different locations.
10028    cx.update_editor(|editor, window, cx| {
10029        let snapshot = editor.snapshot(window, cx);
10030        let cursors = editor.selections.ranges::<usize>(cx);
10031        let languages = cursors
10032            .iter()
10033            .map(|c| snapshot.language_at(c.start).unwrap().name())
10034            .collect::<Vec<_>>();
10035        assert_eq!(
10036            languages,
10037            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10038        );
10039    });
10040
10041    // Angle brackets autoclose in HTML, but not JavaScript.
10042    cx.update_editor(|editor, window, cx| {
10043        editor.handle_input("<", window, cx);
10044        editor.handle_input("a", window, cx);
10045    });
10046    cx.assert_editor_state(
10047        &r#"
10048            <body><aˇ>
10049                <script>
10050                    var x = 1;<aˇ
10051                </script>
10052            </body><aˇ>
10053        "#
10054        .unindent(),
10055    );
10056
10057    // Curly braces and parens autoclose in both HTML and JavaScript.
10058    cx.update_editor(|editor, window, cx| {
10059        editor.handle_input(" b=", window, cx);
10060        editor.handle_input("{", window, cx);
10061        editor.handle_input("c", window, cx);
10062        editor.handle_input("(", window, cx);
10063    });
10064    cx.assert_editor_state(
10065        &r#"
10066            <body><a b={c(ˇ)}>
10067                <script>
10068                    var x = 1;<a b={c(ˇ)}
10069                </script>
10070            </body><a b={c(ˇ)}>
10071        "#
10072        .unindent(),
10073    );
10074
10075    // Brackets that were already autoclosed are skipped.
10076    cx.update_editor(|editor, window, cx| {
10077        editor.handle_input(")", window, cx);
10078        editor.handle_input("d", window, cx);
10079        editor.handle_input("}", window, cx);
10080    });
10081    cx.assert_editor_state(
10082        &r#"
10083            <body><a b={c()d}ˇ>
10084                <script>
10085                    var x = 1;<a b={c()d}ˇ
10086                </script>
10087            </body><a b={c()d}ˇ>
10088        "#
10089        .unindent(),
10090    );
10091    cx.update_editor(|editor, window, cx| {
10092        editor.handle_input(">", window, cx);
10093    });
10094    cx.assert_editor_state(
10095        &r#"
10096            <body><a b={c()d}>ˇ
10097                <script>
10098                    var x = 1;<a b={c()d}>ˇ
10099                </script>
10100            </body><a b={c()d}>ˇ
10101        "#
10102        .unindent(),
10103    );
10104
10105    // Reset
10106    cx.set_state(
10107        &r#"
10108            <body>ˇ
10109                <script>
10110                    var x = 1;ˇ
10111                </script>
10112            </body>ˇ
10113        "#
10114        .unindent(),
10115    );
10116
10117    cx.update_editor(|editor, window, cx| {
10118        editor.handle_input("<", window, cx);
10119    });
10120    cx.assert_editor_state(
10121        &r#"
10122            <body><ˇ>
10123                <script>
10124                    var x = 1;<ˇ
10125                </script>
10126            </body><ˇ>
10127        "#
10128        .unindent(),
10129    );
10130
10131    // When backspacing, the closing angle brackets are removed.
10132    cx.update_editor(|editor, window, cx| {
10133        editor.backspace(&Backspace, window, cx);
10134    });
10135    cx.assert_editor_state(
10136        &r#"
10137            <body>ˇ
10138                <script>
10139                    var x = 1;ˇ
10140                </script>
10141            </body>ˇ
10142        "#
10143        .unindent(),
10144    );
10145
10146    // Block comments autoclose in JavaScript, but not HTML.
10147    cx.update_editor(|editor, window, cx| {
10148        editor.handle_input("/", window, cx);
10149        editor.handle_input("*", window, cx);
10150    });
10151    cx.assert_editor_state(
10152        &r#"
10153            <body>/*ˇ
10154                <script>
10155                    var x = 1;/*ˇ */
10156                </script>
10157            </body>/*ˇ
10158        "#
10159        .unindent(),
10160    );
10161}
10162
10163#[gpui::test]
10164async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10165    init_test(cx, |_| {});
10166
10167    let mut cx = EditorTestContext::new(cx).await;
10168
10169    let rust_language = Arc::new(
10170        Language::new(
10171            LanguageConfig {
10172                name: "Rust".into(),
10173                brackets: serde_json::from_value(json!([
10174                    { "start": "{", "end": "}", "close": true, "newline": true },
10175                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10176                ]))
10177                .unwrap(),
10178                autoclose_before: "})]>".into(),
10179                ..Default::default()
10180            },
10181            Some(tree_sitter_rust::LANGUAGE.into()),
10182        )
10183        .with_override_query("(string_literal) @string")
10184        .unwrap(),
10185    );
10186
10187    cx.language_registry().add(rust_language.clone());
10188    cx.update_buffer(|buffer, cx| {
10189        buffer.set_language(Some(rust_language), cx);
10190    });
10191
10192    cx.set_state(
10193        &r#"
10194            let x = ˇ
10195        "#
10196        .unindent(),
10197    );
10198
10199    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10200    cx.update_editor(|editor, window, cx| {
10201        editor.handle_input("\"", window, cx);
10202    });
10203    cx.assert_editor_state(
10204        &r#"
10205            let x = "ˇ"
10206        "#
10207        .unindent(),
10208    );
10209
10210    // Inserting another quotation mark. The cursor moves across the existing
10211    // automatically-inserted quotation mark.
10212    cx.update_editor(|editor, window, cx| {
10213        editor.handle_input("\"", window, cx);
10214    });
10215    cx.assert_editor_state(
10216        &r#"
10217            let x = ""ˇ
10218        "#
10219        .unindent(),
10220    );
10221
10222    // Reset
10223    cx.set_state(
10224        &r#"
10225            let x = ˇ
10226        "#
10227        .unindent(),
10228    );
10229
10230    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10231    cx.update_editor(|editor, window, cx| {
10232        editor.handle_input("\"", window, cx);
10233        editor.handle_input(" ", window, cx);
10234        editor.move_left(&Default::default(), window, cx);
10235        editor.handle_input("\\", window, cx);
10236        editor.handle_input("\"", window, cx);
10237    });
10238    cx.assert_editor_state(
10239        &r#"
10240            let x = "\"ˇ "
10241        "#
10242        .unindent(),
10243    );
10244
10245    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10246    // mark. Nothing is inserted.
10247    cx.update_editor(|editor, window, cx| {
10248        editor.move_right(&Default::default(), window, cx);
10249        editor.handle_input("\"", window, cx);
10250    });
10251    cx.assert_editor_state(
10252        &r#"
10253            let x = "\" "ˇ
10254        "#
10255        .unindent(),
10256    );
10257}
10258
10259#[gpui::test]
10260async fn test_surround_with_pair(cx: &mut TestAppContext) {
10261    init_test(cx, |_| {});
10262
10263    let language = Arc::new(Language::new(
10264        LanguageConfig {
10265            brackets: BracketPairConfig {
10266                pairs: vec![
10267                    BracketPair {
10268                        start: "{".to_string(),
10269                        end: "}".to_string(),
10270                        close: true,
10271                        surround: true,
10272                        newline: true,
10273                    },
10274                    BracketPair {
10275                        start: "/* ".to_string(),
10276                        end: "*/".to_string(),
10277                        close: true,
10278                        surround: true,
10279                        ..Default::default()
10280                    },
10281                ],
10282                ..Default::default()
10283            },
10284            ..Default::default()
10285        },
10286        Some(tree_sitter_rust::LANGUAGE.into()),
10287    ));
10288
10289    let text = r#"
10290        a
10291        b
10292        c
10293    "#
10294    .unindent();
10295
10296    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10297    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10298    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10299    editor
10300        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10301        .await;
10302
10303    editor.update_in(cx, |editor, window, cx| {
10304        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10305            s.select_display_ranges([
10306                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10307                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10308                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10309            ])
10310        });
10311
10312        editor.handle_input("{", window, cx);
10313        editor.handle_input("{", window, cx);
10314        editor.handle_input("{", window, cx);
10315        assert_eq!(
10316            editor.text(cx),
10317            "
10318                {{{a}}}
10319                {{{b}}}
10320                {{{c}}}
10321            "
10322            .unindent()
10323        );
10324        assert_eq!(
10325            editor.selections.display_ranges(cx),
10326            [
10327                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10328                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10329                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10330            ]
10331        );
10332
10333        editor.undo(&Undo, window, cx);
10334        editor.undo(&Undo, window, cx);
10335        editor.undo(&Undo, window, cx);
10336        assert_eq!(
10337            editor.text(cx),
10338            "
10339                a
10340                b
10341                c
10342            "
10343            .unindent()
10344        );
10345        assert_eq!(
10346            editor.selections.display_ranges(cx),
10347            [
10348                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10349                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10350                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10351            ]
10352        );
10353
10354        // Ensure inserting the first character of a multi-byte bracket pair
10355        // doesn't surround the selections with the bracket.
10356        editor.handle_input("/", window, cx);
10357        assert_eq!(
10358            editor.text(cx),
10359            "
10360                /
10361                /
10362                /
10363            "
10364            .unindent()
10365        );
10366        assert_eq!(
10367            editor.selections.display_ranges(cx),
10368            [
10369                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10370                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10371                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10372            ]
10373        );
10374
10375        editor.undo(&Undo, window, cx);
10376        assert_eq!(
10377            editor.text(cx),
10378            "
10379                a
10380                b
10381                c
10382            "
10383            .unindent()
10384        );
10385        assert_eq!(
10386            editor.selections.display_ranges(cx),
10387            [
10388                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10389                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10390                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10391            ]
10392        );
10393
10394        // Ensure inserting the last character of a multi-byte bracket pair
10395        // doesn't surround the selections with the bracket.
10396        editor.handle_input("*", window, cx);
10397        assert_eq!(
10398            editor.text(cx),
10399            "
10400                *
10401                *
10402                *
10403            "
10404            .unindent()
10405        );
10406        assert_eq!(
10407            editor.selections.display_ranges(cx),
10408            [
10409                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10410                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10411                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10412            ]
10413        );
10414    });
10415}
10416
10417#[gpui::test]
10418async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10419    init_test(cx, |_| {});
10420
10421    let language = Arc::new(Language::new(
10422        LanguageConfig {
10423            brackets: BracketPairConfig {
10424                pairs: vec![BracketPair {
10425                    start: "{".to_string(),
10426                    end: "}".to_string(),
10427                    close: true,
10428                    surround: true,
10429                    newline: true,
10430                }],
10431                ..Default::default()
10432            },
10433            autoclose_before: "}".to_string(),
10434            ..Default::default()
10435        },
10436        Some(tree_sitter_rust::LANGUAGE.into()),
10437    ));
10438
10439    let text = r#"
10440        a
10441        b
10442        c
10443    "#
10444    .unindent();
10445
10446    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10447    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10448    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10449    editor
10450        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10451        .await;
10452
10453    editor.update_in(cx, |editor, window, cx| {
10454        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10455            s.select_ranges([
10456                Point::new(0, 1)..Point::new(0, 1),
10457                Point::new(1, 1)..Point::new(1, 1),
10458                Point::new(2, 1)..Point::new(2, 1),
10459            ])
10460        });
10461
10462        editor.handle_input("{", window, cx);
10463        editor.handle_input("{", window, cx);
10464        editor.handle_input("_", window, cx);
10465        assert_eq!(
10466            editor.text(cx),
10467            "
10468                a{{_}}
10469                b{{_}}
10470                c{{_}}
10471            "
10472            .unindent()
10473        );
10474        assert_eq!(
10475            editor.selections.ranges::<Point>(cx),
10476            [
10477                Point::new(0, 4)..Point::new(0, 4),
10478                Point::new(1, 4)..Point::new(1, 4),
10479                Point::new(2, 4)..Point::new(2, 4)
10480            ]
10481        );
10482
10483        editor.backspace(&Default::default(), window, cx);
10484        editor.backspace(&Default::default(), window, cx);
10485        assert_eq!(
10486            editor.text(cx),
10487            "
10488                a{}
10489                b{}
10490                c{}
10491            "
10492            .unindent()
10493        );
10494        assert_eq!(
10495            editor.selections.ranges::<Point>(cx),
10496            [
10497                Point::new(0, 2)..Point::new(0, 2),
10498                Point::new(1, 2)..Point::new(1, 2),
10499                Point::new(2, 2)..Point::new(2, 2)
10500            ]
10501        );
10502
10503        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10504        assert_eq!(
10505            editor.text(cx),
10506            "
10507                a
10508                b
10509                c
10510            "
10511            .unindent()
10512        );
10513        assert_eq!(
10514            editor.selections.ranges::<Point>(cx),
10515            [
10516                Point::new(0, 1)..Point::new(0, 1),
10517                Point::new(1, 1)..Point::new(1, 1),
10518                Point::new(2, 1)..Point::new(2, 1)
10519            ]
10520        );
10521    });
10522}
10523
10524#[gpui::test]
10525async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10526    init_test(cx, |settings| {
10527        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10528    });
10529
10530    let mut cx = EditorTestContext::new(cx).await;
10531
10532    let language = Arc::new(Language::new(
10533        LanguageConfig {
10534            brackets: BracketPairConfig {
10535                pairs: vec![
10536                    BracketPair {
10537                        start: "{".to_string(),
10538                        end: "}".to_string(),
10539                        close: true,
10540                        surround: true,
10541                        newline: true,
10542                    },
10543                    BracketPair {
10544                        start: "(".to_string(),
10545                        end: ")".to_string(),
10546                        close: true,
10547                        surround: true,
10548                        newline: true,
10549                    },
10550                    BracketPair {
10551                        start: "[".to_string(),
10552                        end: "]".to_string(),
10553                        close: false,
10554                        surround: true,
10555                        newline: true,
10556                    },
10557                ],
10558                ..Default::default()
10559            },
10560            autoclose_before: "})]".to_string(),
10561            ..Default::default()
10562        },
10563        Some(tree_sitter_rust::LANGUAGE.into()),
10564    ));
10565
10566    cx.language_registry().add(language.clone());
10567    cx.update_buffer(|buffer, cx| {
10568        buffer.set_language(Some(language), cx);
10569    });
10570
10571    cx.set_state(
10572        &"
10573            {(ˇ)}
10574            [[ˇ]]
10575            {(ˇ)}
10576        "
10577        .unindent(),
10578    );
10579
10580    cx.update_editor(|editor, window, cx| {
10581        editor.backspace(&Default::default(), window, cx);
10582        editor.backspace(&Default::default(), window, cx);
10583    });
10584
10585    cx.assert_editor_state(
10586        &"
10587            ˇ
10588            ˇ]]
10589            ˇ
10590        "
10591        .unindent(),
10592    );
10593
10594    cx.update_editor(|editor, window, cx| {
10595        editor.handle_input("{", window, cx);
10596        editor.handle_input("{", window, cx);
10597        editor.move_right(&MoveRight, window, cx);
10598        editor.move_right(&MoveRight, window, cx);
10599        editor.move_left(&MoveLeft, window, cx);
10600        editor.move_left(&MoveLeft, window, cx);
10601        editor.backspace(&Default::default(), window, cx);
10602    });
10603
10604    cx.assert_editor_state(
10605        &"
10606            {ˇ}
10607            {ˇ}]]
10608            {ˇ}
10609        "
10610        .unindent(),
10611    );
10612
10613    cx.update_editor(|editor, window, cx| {
10614        editor.backspace(&Default::default(), window, cx);
10615    });
10616
10617    cx.assert_editor_state(
10618        &"
10619            ˇ
10620            ˇ]]
10621            ˇ
10622        "
10623        .unindent(),
10624    );
10625}
10626
10627#[gpui::test]
10628async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10629    init_test(cx, |_| {});
10630
10631    let language = Arc::new(Language::new(
10632        LanguageConfig::default(),
10633        Some(tree_sitter_rust::LANGUAGE.into()),
10634    ));
10635
10636    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10637    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10638    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10639    editor
10640        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10641        .await;
10642
10643    editor.update_in(cx, |editor, window, cx| {
10644        editor.set_auto_replace_emoji_shortcode(true);
10645
10646        editor.handle_input("Hello ", window, cx);
10647        editor.handle_input(":wave", window, cx);
10648        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10649
10650        editor.handle_input(":", window, cx);
10651        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10652
10653        editor.handle_input(" :smile", window, cx);
10654        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10655
10656        editor.handle_input(":", window, cx);
10657        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10658
10659        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10660        editor.handle_input(":wave", window, cx);
10661        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10662
10663        editor.handle_input(":", window, cx);
10664        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10665
10666        editor.handle_input(":1", window, cx);
10667        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10668
10669        editor.handle_input(":", window, cx);
10670        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
10671
10672        // Ensure shortcode does not get replaced when it is part of a word
10673        editor.handle_input(" Test:wave", window, cx);
10674        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
10675
10676        editor.handle_input(":", window, cx);
10677        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
10678
10679        editor.set_auto_replace_emoji_shortcode(false);
10680
10681        // Ensure shortcode does not get replaced when auto replace is off
10682        editor.handle_input(" :wave", window, cx);
10683        assert_eq!(
10684            editor.text(cx),
10685            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
10686        );
10687
10688        editor.handle_input(":", window, cx);
10689        assert_eq!(
10690            editor.text(cx),
10691            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
10692        );
10693    });
10694}
10695
10696#[gpui::test]
10697async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
10698    init_test(cx, |_| {});
10699
10700    let (text, insertion_ranges) = marked_text_ranges(
10701        indoc! {"
10702            ˇ
10703        "},
10704        false,
10705    );
10706
10707    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
10708    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10709
10710    _ = editor.update_in(cx, |editor, window, cx| {
10711        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
10712
10713        editor
10714            .insert_snippet(&insertion_ranges, snippet, window, cx)
10715            .unwrap();
10716
10717        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
10718            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
10719            assert_eq!(editor.text(cx), expected_text);
10720            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
10721        }
10722
10723        assert(
10724            editor,
10725            cx,
10726            indoc! {"
10727            type «» =•
10728            "},
10729        );
10730
10731        assert!(editor.context_menu_visible(), "There should be a matches");
10732    });
10733}
10734
10735#[gpui::test]
10736async fn test_snippets(cx: &mut TestAppContext) {
10737    init_test(cx, |_| {});
10738
10739    let mut cx = EditorTestContext::new(cx).await;
10740
10741    cx.set_state(indoc! {"
10742        a.ˇ b
10743        a.ˇ b
10744        a.ˇ b
10745    "});
10746
10747    cx.update_editor(|editor, window, cx| {
10748        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
10749        let insertion_ranges = editor
10750            .selections
10751            .all(cx)
10752            .iter()
10753            .map(|s| s.range())
10754            .collect::<Vec<_>>();
10755        editor
10756            .insert_snippet(&insertion_ranges, snippet, window, cx)
10757            .unwrap();
10758    });
10759
10760    cx.assert_editor_state(indoc! {"
10761        a.f(«oneˇ», two, «threeˇ») b
10762        a.f(«oneˇ», two, «threeˇ») b
10763        a.f(«oneˇ», two, «threeˇ») b
10764    "});
10765
10766    // Can't move earlier than the first tab stop
10767    cx.update_editor(|editor, window, cx| {
10768        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10769    });
10770    cx.assert_editor_state(indoc! {"
10771        a.f(«oneˇ», two, «threeˇ») b
10772        a.f(«oneˇ», two, «threeˇ») b
10773        a.f(«oneˇ», two, «threeˇ») b
10774    "});
10775
10776    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10777    cx.assert_editor_state(indoc! {"
10778        a.f(one, «twoˇ», three) b
10779        a.f(one, «twoˇ», three) b
10780        a.f(one, «twoˇ», three) b
10781    "});
10782
10783    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
10784    cx.assert_editor_state(indoc! {"
10785        a.f(«oneˇ», two, «threeˇ») b
10786        a.f(«oneˇ», two, «threeˇ») b
10787        a.f(«oneˇ», two, «threeˇ») b
10788    "});
10789
10790    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10791    cx.assert_editor_state(indoc! {"
10792        a.f(one, «twoˇ», three) b
10793        a.f(one, «twoˇ», three) b
10794        a.f(one, «twoˇ», three) b
10795    "});
10796    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10797    cx.assert_editor_state(indoc! {"
10798        a.f(one, two, three)ˇ b
10799        a.f(one, two, three)ˇ b
10800        a.f(one, two, three)ˇ b
10801    "});
10802
10803    // As soon as the last tab stop is reached, snippet state is gone
10804    cx.update_editor(|editor, window, cx| {
10805        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10806    });
10807    cx.assert_editor_state(indoc! {"
10808        a.f(one, two, three)ˇ b
10809        a.f(one, two, three)ˇ b
10810        a.f(one, two, three)ˇ b
10811    "});
10812}
10813
10814#[gpui::test]
10815async fn test_snippet_indentation(cx: &mut TestAppContext) {
10816    init_test(cx, |_| {});
10817
10818    let mut cx = EditorTestContext::new(cx).await;
10819
10820    cx.update_editor(|editor, window, cx| {
10821        let snippet = Snippet::parse(indoc! {"
10822            /*
10823             * Multiline comment with leading indentation
10824             *
10825             * $1
10826             */
10827            $0"})
10828        .unwrap();
10829        let insertion_ranges = editor
10830            .selections
10831            .all(cx)
10832            .iter()
10833            .map(|s| s.range())
10834            .collect::<Vec<_>>();
10835        editor
10836            .insert_snippet(&insertion_ranges, snippet, window, cx)
10837            .unwrap();
10838    });
10839
10840    cx.assert_editor_state(indoc! {"
10841        /*
10842         * Multiline comment with leading indentation
10843         *
10844         * ˇ
10845         */
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        /*
10851         * Multiline comment with leading indentation
10852         *
10853         *•
10854         */
10855        ˇ"});
10856}
10857
10858#[gpui::test]
10859async fn test_document_format_during_save(cx: &mut TestAppContext) {
10860    init_test(cx, |_| {});
10861
10862    let fs = FakeFs::new(cx.executor());
10863    fs.insert_file(path!("/file.rs"), Default::default()).await;
10864
10865    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
10866
10867    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10868    language_registry.add(rust_lang());
10869    let mut fake_servers = language_registry.register_fake_lsp(
10870        "Rust",
10871        FakeLspAdapter {
10872            capabilities: lsp::ServerCapabilities {
10873                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10874                ..Default::default()
10875            },
10876            ..Default::default()
10877        },
10878    );
10879
10880    let buffer = project
10881        .update(cx, |project, cx| {
10882            project.open_local_buffer(path!("/file.rs"), cx)
10883        })
10884        .await
10885        .unwrap();
10886
10887    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10888    let (editor, cx) = cx.add_window_view(|window, cx| {
10889        build_editor_with_project(project.clone(), buffer, window, cx)
10890    });
10891    editor.update_in(cx, |editor, window, cx| {
10892        editor.set_text("one\ntwo\nthree\n", window, cx)
10893    });
10894    assert!(cx.read(|cx| editor.is_dirty(cx)));
10895
10896    cx.executor().start_waiting();
10897    let fake_server = fake_servers.next().await.unwrap();
10898
10899    {
10900        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10901            move |params, _| async move {
10902                assert_eq!(
10903                    params.text_document.uri,
10904                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10905                );
10906                assert_eq!(params.options.tab_size, 4);
10907                Ok(Some(vec![lsp::TextEdit::new(
10908                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10909                    ", ".to_string(),
10910                )]))
10911            },
10912        );
10913        let save = editor
10914            .update_in(cx, |editor, window, cx| {
10915                editor.save(
10916                    SaveOptions {
10917                        format: true,
10918                        autosave: false,
10919                    },
10920                    project.clone(),
10921                    window,
10922                    cx,
10923                )
10924            })
10925            .unwrap();
10926        cx.executor().start_waiting();
10927        save.await;
10928
10929        assert_eq!(
10930            editor.update(cx, |editor, cx| editor.text(cx)),
10931            "one, two\nthree\n"
10932        );
10933        assert!(!cx.read(|cx| editor.is_dirty(cx)));
10934    }
10935
10936    {
10937        editor.update_in(cx, |editor, window, cx| {
10938            editor.set_text("one\ntwo\nthree\n", window, cx)
10939        });
10940        assert!(cx.read(|cx| editor.is_dirty(cx)));
10941
10942        // Ensure we can still save even if formatting hangs.
10943        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10944            move |params, _| async move {
10945                assert_eq!(
10946                    params.text_document.uri,
10947                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10948                );
10949                futures::future::pending::<()>().await;
10950                unreachable!()
10951            },
10952        );
10953        let save = editor
10954            .update_in(cx, |editor, window, cx| {
10955                editor.save(
10956                    SaveOptions {
10957                        format: true,
10958                        autosave: false,
10959                    },
10960                    project.clone(),
10961                    window,
10962                    cx,
10963                )
10964            })
10965            .unwrap();
10966        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10967        cx.executor().start_waiting();
10968        save.await;
10969        assert_eq!(
10970            editor.update(cx, |editor, cx| editor.text(cx)),
10971            "one\ntwo\nthree\n"
10972        );
10973    }
10974
10975    // Set rust language override and assert overridden tabsize is sent to language server
10976    update_test_language_settings(cx, |settings| {
10977        settings.languages.0.insert(
10978            "Rust".into(),
10979            LanguageSettingsContent {
10980                tab_size: NonZeroU32::new(8),
10981                ..Default::default()
10982            },
10983        );
10984    });
10985
10986    {
10987        editor.update_in(cx, |editor, window, cx| {
10988            editor.set_text("somehting_new\n", window, cx)
10989        });
10990        assert!(cx.read(|cx| editor.is_dirty(cx)));
10991        let _formatting_request_signal = fake_server
10992            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10993                assert_eq!(
10994                    params.text_document.uri,
10995                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10996                );
10997                assert_eq!(params.options.tab_size, 8);
10998                Ok(Some(vec![]))
10999            });
11000        let save = editor
11001            .update_in(cx, |editor, window, cx| {
11002                editor.save(
11003                    SaveOptions {
11004                        format: true,
11005                        autosave: false,
11006                    },
11007                    project.clone(),
11008                    window,
11009                    cx,
11010                )
11011            })
11012            .unwrap();
11013        cx.executor().start_waiting();
11014        save.await;
11015    }
11016}
11017
11018#[gpui::test]
11019async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11020    init_test(cx, |settings| {
11021        settings.defaults.ensure_final_newline_on_save = Some(false);
11022    });
11023
11024    let fs = FakeFs::new(cx.executor());
11025    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11026
11027    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11028
11029    let buffer = project
11030        .update(cx, |project, cx| {
11031            project.open_local_buffer(path!("/file.txt"), cx)
11032        })
11033        .await
11034        .unwrap();
11035
11036    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11037    let (editor, cx) = cx.add_window_view(|window, cx| {
11038        build_editor_with_project(project.clone(), buffer, window, cx)
11039    });
11040    editor.update_in(cx, |editor, window, cx| {
11041        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11042            s.select_ranges([0..0])
11043        });
11044    });
11045    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11046
11047    editor.update_in(cx, |editor, window, cx| {
11048        editor.handle_input("\n", window, cx)
11049    });
11050    cx.run_until_parked();
11051    save(&editor, &project, cx).await;
11052    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11053
11054    editor.update_in(cx, |editor, window, cx| {
11055        editor.undo(&Default::default(), window, cx);
11056    });
11057    save(&editor, &project, cx).await;
11058    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11059
11060    editor.update_in(cx, |editor, window, cx| {
11061        editor.redo(&Default::default(), window, cx);
11062    });
11063    cx.run_until_parked();
11064    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11065
11066    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11067        let save = editor
11068            .update_in(cx, |editor, window, cx| {
11069                editor.save(
11070                    SaveOptions {
11071                        format: true,
11072                        autosave: false,
11073                    },
11074                    project.clone(),
11075                    window,
11076                    cx,
11077                )
11078            })
11079            .unwrap();
11080        cx.executor().start_waiting();
11081        save.await;
11082        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11083    }
11084}
11085
11086#[gpui::test]
11087async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11088    init_test(cx, |_| {});
11089
11090    let cols = 4;
11091    let rows = 10;
11092    let sample_text_1 = sample_text(rows, cols, 'a');
11093    assert_eq!(
11094        sample_text_1,
11095        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11096    );
11097    let sample_text_2 = sample_text(rows, cols, 'l');
11098    assert_eq!(
11099        sample_text_2,
11100        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11101    );
11102    let sample_text_3 = sample_text(rows, cols, 'v');
11103    assert_eq!(
11104        sample_text_3,
11105        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11106    );
11107
11108    let fs = FakeFs::new(cx.executor());
11109    fs.insert_tree(
11110        path!("/a"),
11111        json!({
11112            "main.rs": sample_text_1,
11113            "other.rs": sample_text_2,
11114            "lib.rs": sample_text_3,
11115        }),
11116    )
11117    .await;
11118
11119    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11120    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11121    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11122
11123    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11124    language_registry.add(rust_lang());
11125    let mut fake_servers = language_registry.register_fake_lsp(
11126        "Rust",
11127        FakeLspAdapter {
11128            capabilities: lsp::ServerCapabilities {
11129                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11130                ..Default::default()
11131            },
11132            ..Default::default()
11133        },
11134    );
11135
11136    let worktree = project.update(cx, |project, cx| {
11137        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11138        assert_eq!(worktrees.len(), 1);
11139        worktrees.pop().unwrap()
11140    });
11141    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11142
11143    let buffer_1 = project
11144        .update(cx, |project, cx| {
11145            project.open_buffer((worktree_id, "main.rs"), cx)
11146        })
11147        .await
11148        .unwrap();
11149    let buffer_2 = project
11150        .update(cx, |project, cx| {
11151            project.open_buffer((worktree_id, "other.rs"), cx)
11152        })
11153        .await
11154        .unwrap();
11155    let buffer_3 = project
11156        .update(cx, |project, cx| {
11157            project.open_buffer((worktree_id, "lib.rs"), cx)
11158        })
11159        .await
11160        .unwrap();
11161
11162    let multi_buffer = cx.new(|cx| {
11163        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11164        multi_buffer.push_excerpts(
11165            buffer_1.clone(),
11166            [
11167                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11168                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11169                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11170            ],
11171            cx,
11172        );
11173        multi_buffer.push_excerpts(
11174            buffer_2.clone(),
11175            [
11176                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11177                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11178                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11179            ],
11180            cx,
11181        );
11182        multi_buffer.push_excerpts(
11183            buffer_3.clone(),
11184            [
11185                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11186                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11187                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11188            ],
11189            cx,
11190        );
11191        multi_buffer
11192    });
11193    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11194        Editor::new(
11195            EditorMode::full(),
11196            multi_buffer,
11197            Some(project.clone()),
11198            window,
11199            cx,
11200        )
11201    });
11202
11203    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11204        editor.change_selections(
11205            SelectionEffects::scroll(Autoscroll::Next),
11206            window,
11207            cx,
11208            |s| s.select_ranges(Some(1..2)),
11209        );
11210        editor.insert("|one|two|three|", window, cx);
11211    });
11212    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11213    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11214        editor.change_selections(
11215            SelectionEffects::scroll(Autoscroll::Next),
11216            window,
11217            cx,
11218            |s| s.select_ranges(Some(60..70)),
11219        );
11220        editor.insert("|four|five|six|", window, cx);
11221    });
11222    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11223
11224    // First two buffers should be edited, but not the third one.
11225    assert_eq!(
11226        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11227        "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}",
11228    );
11229    buffer_1.update(cx, |buffer, _| {
11230        assert!(buffer.is_dirty());
11231        assert_eq!(
11232            buffer.text(),
11233            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11234        )
11235    });
11236    buffer_2.update(cx, |buffer, _| {
11237        assert!(buffer.is_dirty());
11238        assert_eq!(
11239            buffer.text(),
11240            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11241        )
11242    });
11243    buffer_3.update(cx, |buffer, _| {
11244        assert!(!buffer.is_dirty());
11245        assert_eq!(buffer.text(), sample_text_3,)
11246    });
11247    cx.executor().run_until_parked();
11248
11249    cx.executor().start_waiting();
11250    let save = multi_buffer_editor
11251        .update_in(cx, |editor, window, cx| {
11252            editor.save(
11253                SaveOptions {
11254                    format: true,
11255                    autosave: false,
11256                },
11257                project.clone(),
11258                window,
11259                cx,
11260            )
11261        })
11262        .unwrap();
11263
11264    let fake_server = fake_servers.next().await.unwrap();
11265    fake_server
11266        .server
11267        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11268            Ok(Some(vec![lsp::TextEdit::new(
11269                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11270                format!("[{} formatted]", params.text_document.uri),
11271            )]))
11272        })
11273        .detach();
11274    save.await;
11275
11276    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11277    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11278    assert_eq!(
11279        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11280        uri!(
11281            "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}"
11282        ),
11283    );
11284    buffer_1.update(cx, |buffer, _| {
11285        assert!(!buffer.is_dirty());
11286        assert_eq!(
11287            buffer.text(),
11288            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11289        )
11290    });
11291    buffer_2.update(cx, |buffer, _| {
11292        assert!(!buffer.is_dirty());
11293        assert_eq!(
11294            buffer.text(),
11295            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11296        )
11297    });
11298    buffer_3.update(cx, |buffer, _| {
11299        assert!(!buffer.is_dirty());
11300        assert_eq!(buffer.text(), sample_text_3,)
11301    });
11302}
11303
11304#[gpui::test]
11305async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11306    init_test(cx, |_| {});
11307
11308    let fs = FakeFs::new(cx.executor());
11309    fs.insert_tree(
11310        path!("/dir"),
11311        json!({
11312            "file1.rs": "fn main() { println!(\"hello\"); }",
11313            "file2.rs": "fn test() { println!(\"test\"); }",
11314            "file3.rs": "fn other() { println!(\"other\"); }\n",
11315        }),
11316    )
11317    .await;
11318
11319    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11320    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11321    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11322
11323    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11324    language_registry.add(rust_lang());
11325
11326    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11327    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11328
11329    // Open three buffers
11330    let buffer_1 = project
11331        .update(cx, |project, cx| {
11332            project.open_buffer((worktree_id, "file1.rs"), cx)
11333        })
11334        .await
11335        .unwrap();
11336    let buffer_2 = project
11337        .update(cx, |project, cx| {
11338            project.open_buffer((worktree_id, "file2.rs"), cx)
11339        })
11340        .await
11341        .unwrap();
11342    let buffer_3 = project
11343        .update(cx, |project, cx| {
11344            project.open_buffer((worktree_id, "file3.rs"), cx)
11345        })
11346        .await
11347        .unwrap();
11348
11349    // Create a multi-buffer with all three buffers
11350    let multi_buffer = cx.new(|cx| {
11351        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11352        multi_buffer.push_excerpts(
11353            buffer_1.clone(),
11354            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11355            cx,
11356        );
11357        multi_buffer.push_excerpts(
11358            buffer_2.clone(),
11359            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11360            cx,
11361        );
11362        multi_buffer.push_excerpts(
11363            buffer_3.clone(),
11364            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11365            cx,
11366        );
11367        multi_buffer
11368    });
11369
11370    let editor = cx.new_window_entity(|window, cx| {
11371        Editor::new(
11372            EditorMode::full(),
11373            multi_buffer,
11374            Some(project.clone()),
11375            window,
11376            cx,
11377        )
11378    });
11379
11380    // Edit only the first buffer
11381    editor.update_in(cx, |editor, window, cx| {
11382        editor.change_selections(
11383            SelectionEffects::scroll(Autoscroll::Next),
11384            window,
11385            cx,
11386            |s| s.select_ranges(Some(10..10)),
11387        );
11388        editor.insert("// edited", window, cx);
11389    });
11390
11391    // Verify that only buffer 1 is dirty
11392    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11393    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11394    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11395
11396    // Get write counts after file creation (files were created with initial content)
11397    // We expect each file to have been written once during creation
11398    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11399    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11400    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11401
11402    // Perform autosave
11403    let save_task = editor.update_in(cx, |editor, window, cx| {
11404        editor.save(
11405            SaveOptions {
11406                format: true,
11407                autosave: true,
11408            },
11409            project.clone(),
11410            window,
11411            cx,
11412        )
11413    });
11414    save_task.await.unwrap();
11415
11416    // Only the dirty buffer should have been saved
11417    assert_eq!(
11418        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11419        1,
11420        "Buffer 1 was dirty, so it should have been written once during autosave"
11421    );
11422    assert_eq!(
11423        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11424        0,
11425        "Buffer 2 was clean, so it should not have been written during autosave"
11426    );
11427    assert_eq!(
11428        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11429        0,
11430        "Buffer 3 was clean, so it should not have been written during autosave"
11431    );
11432
11433    // Verify buffer states after autosave
11434    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11435    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11436    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11437
11438    // Now perform a manual save (format = true)
11439    let save_task = editor.update_in(cx, |editor, window, cx| {
11440        editor.save(
11441            SaveOptions {
11442                format: true,
11443                autosave: false,
11444            },
11445            project.clone(),
11446            window,
11447            cx,
11448        )
11449    });
11450    save_task.await.unwrap();
11451
11452    // During manual save, clean buffers don't get written to disk
11453    // They just get did_save called for language server notifications
11454    assert_eq!(
11455        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11456        1,
11457        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11458    );
11459    assert_eq!(
11460        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11461        0,
11462        "Buffer 2 should not have been written at all"
11463    );
11464    assert_eq!(
11465        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11466        0,
11467        "Buffer 3 should not have been written at all"
11468    );
11469}
11470
11471async fn setup_range_format_test(
11472    cx: &mut TestAppContext,
11473) -> (
11474    Entity<Project>,
11475    Entity<Editor>,
11476    &mut gpui::VisualTestContext,
11477    lsp::FakeLanguageServer,
11478) {
11479    init_test(cx, |_| {});
11480
11481    let fs = FakeFs::new(cx.executor());
11482    fs.insert_file(path!("/file.rs"), Default::default()).await;
11483
11484    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11485
11486    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11487    language_registry.add(rust_lang());
11488    let mut fake_servers = language_registry.register_fake_lsp(
11489        "Rust",
11490        FakeLspAdapter {
11491            capabilities: lsp::ServerCapabilities {
11492                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11493                ..lsp::ServerCapabilities::default()
11494            },
11495            ..FakeLspAdapter::default()
11496        },
11497    );
11498
11499    let buffer = project
11500        .update(cx, |project, cx| {
11501            project.open_local_buffer(path!("/file.rs"), cx)
11502        })
11503        .await
11504        .unwrap();
11505
11506    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11507    let (editor, cx) = cx.add_window_view(|window, cx| {
11508        build_editor_with_project(project.clone(), buffer, window, cx)
11509    });
11510
11511    cx.executor().start_waiting();
11512    let fake_server = fake_servers.next().await.unwrap();
11513
11514    (project, editor, cx, fake_server)
11515}
11516
11517#[gpui::test]
11518async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11519    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11520
11521    editor.update_in(cx, |editor, window, cx| {
11522        editor.set_text("one\ntwo\nthree\n", window, cx)
11523    });
11524    assert!(cx.read(|cx| editor.is_dirty(cx)));
11525
11526    let save = editor
11527        .update_in(cx, |editor, window, cx| {
11528            editor.save(
11529                SaveOptions {
11530                    format: true,
11531                    autosave: false,
11532                },
11533                project.clone(),
11534                window,
11535                cx,
11536            )
11537        })
11538        .unwrap();
11539    fake_server
11540        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11541            assert_eq!(
11542                params.text_document.uri,
11543                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11544            );
11545            assert_eq!(params.options.tab_size, 4);
11546            Ok(Some(vec![lsp::TextEdit::new(
11547                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11548                ", ".to_string(),
11549            )]))
11550        })
11551        .next()
11552        .await;
11553    cx.executor().start_waiting();
11554    save.await;
11555    assert_eq!(
11556        editor.update(cx, |editor, cx| editor.text(cx)),
11557        "one, two\nthree\n"
11558    );
11559    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11560}
11561
11562#[gpui::test]
11563async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11564    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11565
11566    editor.update_in(cx, |editor, window, cx| {
11567        editor.set_text("one\ntwo\nthree\n", window, cx)
11568    });
11569    assert!(cx.read(|cx| editor.is_dirty(cx)));
11570
11571    // Test that save still works when formatting hangs
11572    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11573        move |params, _| async move {
11574            assert_eq!(
11575                params.text_document.uri,
11576                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11577            );
11578            futures::future::pending::<()>().await;
11579            unreachable!()
11580        },
11581    );
11582    let save = editor
11583        .update_in(cx, |editor, window, cx| {
11584            editor.save(
11585                SaveOptions {
11586                    format: true,
11587                    autosave: false,
11588                },
11589                project.clone(),
11590                window,
11591                cx,
11592            )
11593        })
11594        .unwrap();
11595    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11596    cx.executor().start_waiting();
11597    save.await;
11598    assert_eq!(
11599        editor.update(cx, |editor, cx| editor.text(cx)),
11600        "one\ntwo\nthree\n"
11601    );
11602    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11603}
11604
11605#[gpui::test]
11606async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11607    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11608
11609    // Buffer starts clean, no formatting should be requested
11610    let save = editor
11611        .update_in(cx, |editor, window, cx| {
11612            editor.save(
11613                SaveOptions {
11614                    format: false,
11615                    autosave: false,
11616                },
11617                project.clone(),
11618                window,
11619                cx,
11620            )
11621        })
11622        .unwrap();
11623    let _pending_format_request = fake_server
11624        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11625            panic!("Should not be invoked");
11626        })
11627        .next();
11628    cx.executor().start_waiting();
11629    save.await;
11630    cx.run_until_parked();
11631}
11632
11633#[gpui::test]
11634async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11635    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11636
11637    // Set Rust language override and assert overridden tabsize is sent to language server
11638    update_test_language_settings(cx, |settings| {
11639        settings.languages.0.insert(
11640            "Rust".into(),
11641            LanguageSettingsContent {
11642                tab_size: NonZeroU32::new(8),
11643                ..Default::default()
11644            },
11645        );
11646    });
11647
11648    editor.update_in(cx, |editor, window, cx| {
11649        editor.set_text("something_new\n", window, cx)
11650    });
11651    assert!(cx.read(|cx| editor.is_dirty(cx)));
11652    let save = editor
11653        .update_in(cx, |editor, window, cx| {
11654            editor.save(
11655                SaveOptions {
11656                    format: true,
11657                    autosave: false,
11658                },
11659                project.clone(),
11660                window,
11661                cx,
11662            )
11663        })
11664        .unwrap();
11665    fake_server
11666        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11667            assert_eq!(
11668                params.text_document.uri,
11669                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11670            );
11671            assert_eq!(params.options.tab_size, 8);
11672            Ok(Some(Vec::new()))
11673        })
11674        .next()
11675        .await;
11676    save.await;
11677}
11678
11679#[gpui::test]
11680async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
11681    init_test(cx, |settings| {
11682        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
11683            Formatter::LanguageServer { name: None },
11684        )))
11685    });
11686
11687    let fs = FakeFs::new(cx.executor());
11688    fs.insert_file(path!("/file.rs"), Default::default()).await;
11689
11690    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11691
11692    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11693    language_registry.add(Arc::new(Language::new(
11694        LanguageConfig {
11695            name: "Rust".into(),
11696            matcher: LanguageMatcher {
11697                path_suffixes: vec!["rs".to_string()],
11698                ..Default::default()
11699            },
11700            ..LanguageConfig::default()
11701        },
11702        Some(tree_sitter_rust::LANGUAGE.into()),
11703    )));
11704    update_test_language_settings(cx, |settings| {
11705        // Enable Prettier formatting for the same buffer, and ensure
11706        // LSP is called instead of Prettier.
11707        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
11708    });
11709    let mut fake_servers = language_registry.register_fake_lsp(
11710        "Rust",
11711        FakeLspAdapter {
11712            capabilities: lsp::ServerCapabilities {
11713                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11714                ..Default::default()
11715            },
11716            ..Default::default()
11717        },
11718    );
11719
11720    let buffer = project
11721        .update(cx, |project, cx| {
11722            project.open_local_buffer(path!("/file.rs"), cx)
11723        })
11724        .await
11725        .unwrap();
11726
11727    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11728    let (editor, cx) = cx.add_window_view(|window, cx| {
11729        build_editor_with_project(project.clone(), buffer, window, cx)
11730    });
11731    editor.update_in(cx, |editor, window, cx| {
11732        editor.set_text("one\ntwo\nthree\n", window, cx)
11733    });
11734
11735    cx.executor().start_waiting();
11736    let fake_server = fake_servers.next().await.unwrap();
11737
11738    let format = editor
11739        .update_in(cx, |editor, window, cx| {
11740            editor.perform_format(
11741                project.clone(),
11742                FormatTrigger::Manual,
11743                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11744                window,
11745                cx,
11746            )
11747        })
11748        .unwrap();
11749    fake_server
11750        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11751            assert_eq!(
11752                params.text_document.uri,
11753                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11754            );
11755            assert_eq!(params.options.tab_size, 4);
11756            Ok(Some(vec![lsp::TextEdit::new(
11757                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11758                ", ".to_string(),
11759            )]))
11760        })
11761        .next()
11762        .await;
11763    cx.executor().start_waiting();
11764    format.await;
11765    assert_eq!(
11766        editor.update(cx, |editor, cx| editor.text(cx)),
11767        "one, two\nthree\n"
11768    );
11769
11770    editor.update_in(cx, |editor, window, cx| {
11771        editor.set_text("one\ntwo\nthree\n", window, cx)
11772    });
11773    // Ensure we don't lock if formatting hangs.
11774    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11775        move |params, _| async move {
11776            assert_eq!(
11777                params.text_document.uri,
11778                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11779            );
11780            futures::future::pending::<()>().await;
11781            unreachable!()
11782        },
11783    );
11784    let format = editor
11785        .update_in(cx, |editor, window, cx| {
11786            editor.perform_format(
11787                project,
11788                FormatTrigger::Manual,
11789                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11790                window,
11791                cx,
11792            )
11793        })
11794        .unwrap();
11795    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11796    cx.executor().start_waiting();
11797    format.await;
11798    assert_eq!(
11799        editor.update(cx, |editor, cx| editor.text(cx)),
11800        "one\ntwo\nthree\n"
11801    );
11802}
11803
11804#[gpui::test]
11805async fn test_multiple_formatters(cx: &mut TestAppContext) {
11806    init_test(cx, |settings| {
11807        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
11808        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11809            Formatter::LanguageServer { name: None },
11810            Formatter::CodeActions(
11811                [
11812                    ("code-action-1".into(), true),
11813                    ("code-action-2".into(), true),
11814                ]
11815                .into_iter()
11816                .collect(),
11817            ),
11818        ])))
11819    });
11820
11821    let fs = FakeFs::new(cx.executor());
11822    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
11823        .await;
11824
11825    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11826    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11827    language_registry.add(rust_lang());
11828
11829    let mut fake_servers = language_registry.register_fake_lsp(
11830        "Rust",
11831        FakeLspAdapter {
11832            capabilities: lsp::ServerCapabilities {
11833                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11834                execute_command_provider: Some(lsp::ExecuteCommandOptions {
11835                    commands: vec!["the-command-for-code-action-1".into()],
11836                    ..Default::default()
11837                }),
11838                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11839                ..Default::default()
11840            },
11841            ..Default::default()
11842        },
11843    );
11844
11845    let buffer = project
11846        .update(cx, |project, cx| {
11847            project.open_local_buffer(path!("/file.rs"), cx)
11848        })
11849        .await
11850        .unwrap();
11851
11852    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11853    let (editor, cx) = cx.add_window_view(|window, cx| {
11854        build_editor_with_project(project.clone(), buffer, window, cx)
11855    });
11856
11857    cx.executor().start_waiting();
11858
11859    let fake_server = fake_servers.next().await.unwrap();
11860    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11861        move |_params, _| async move {
11862            Ok(Some(vec![lsp::TextEdit::new(
11863                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11864                "applied-formatting\n".to_string(),
11865            )]))
11866        },
11867    );
11868    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11869        move |params, _| async move {
11870            assert_eq!(
11871                params.context.only,
11872                Some(vec!["code-action-1".into(), "code-action-2".into()])
11873            );
11874            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
11875            Ok(Some(vec![
11876                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11877                    kind: Some("code-action-1".into()),
11878                    edit: Some(lsp::WorkspaceEdit::new(
11879                        [(
11880                            uri.clone(),
11881                            vec![lsp::TextEdit::new(
11882                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11883                                "applied-code-action-1-edit\n".to_string(),
11884                            )],
11885                        )]
11886                        .into_iter()
11887                        .collect(),
11888                    )),
11889                    command: Some(lsp::Command {
11890                        command: "the-command-for-code-action-1".into(),
11891                        ..Default::default()
11892                    }),
11893                    ..Default::default()
11894                }),
11895                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11896                    kind: Some("code-action-2".into()),
11897                    edit: Some(lsp::WorkspaceEdit::new(
11898                        [(
11899                            uri,
11900                            vec![lsp::TextEdit::new(
11901                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11902                                "applied-code-action-2-edit\n".to_string(),
11903                            )],
11904                        )]
11905                        .into_iter()
11906                        .collect(),
11907                    )),
11908                    ..Default::default()
11909                }),
11910            ]))
11911        },
11912    );
11913
11914    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
11915        move |params, _| async move { Ok(params) }
11916    });
11917
11918    let command_lock = Arc::new(futures::lock::Mutex::new(()));
11919    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
11920        let fake = fake_server.clone();
11921        let lock = command_lock.clone();
11922        move |params, _| {
11923            assert_eq!(params.command, "the-command-for-code-action-1");
11924            let fake = fake.clone();
11925            let lock = lock.clone();
11926            async move {
11927                lock.lock().await;
11928                fake.server
11929                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
11930                        label: None,
11931                        edit: lsp::WorkspaceEdit {
11932                            changes: Some(
11933                                [(
11934                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
11935                                    vec![lsp::TextEdit {
11936                                        range: lsp::Range::new(
11937                                            lsp::Position::new(0, 0),
11938                                            lsp::Position::new(0, 0),
11939                                        ),
11940                                        new_text: "applied-code-action-1-command\n".into(),
11941                                    }],
11942                                )]
11943                                .into_iter()
11944                                .collect(),
11945                            ),
11946                            ..Default::default()
11947                        },
11948                    })
11949                    .await
11950                    .into_response()
11951                    .unwrap();
11952                Ok(Some(json!(null)))
11953            }
11954        }
11955    });
11956
11957    cx.executor().start_waiting();
11958    editor
11959        .update_in(cx, |editor, window, cx| {
11960            editor.perform_format(
11961                project.clone(),
11962                FormatTrigger::Manual,
11963                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11964                window,
11965                cx,
11966            )
11967        })
11968        .unwrap()
11969        .await;
11970    editor.update(cx, |editor, cx| {
11971        assert_eq!(
11972            editor.text(cx),
11973            r#"
11974                applied-code-action-2-edit
11975                applied-code-action-1-command
11976                applied-code-action-1-edit
11977                applied-formatting
11978                one
11979                two
11980                three
11981            "#
11982            .unindent()
11983        );
11984    });
11985
11986    editor.update_in(cx, |editor, window, cx| {
11987        editor.undo(&Default::default(), window, cx);
11988        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
11989    });
11990
11991    // Perform a manual edit while waiting for an LSP command
11992    // that's being run as part of a formatting code action.
11993    let lock_guard = command_lock.lock().await;
11994    let format = editor
11995        .update_in(cx, |editor, window, cx| {
11996            editor.perform_format(
11997                project.clone(),
11998                FormatTrigger::Manual,
11999                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12000                window,
12001                cx,
12002            )
12003        })
12004        .unwrap();
12005    cx.run_until_parked();
12006    editor.update(cx, |editor, cx| {
12007        assert_eq!(
12008            editor.text(cx),
12009            r#"
12010                applied-code-action-1-edit
12011                applied-formatting
12012                one
12013                two
12014                three
12015            "#
12016            .unindent()
12017        );
12018
12019        editor.buffer.update(cx, |buffer, cx| {
12020            let ix = buffer.len(cx);
12021            buffer.edit([(ix..ix, "edited\n")], None, cx);
12022        });
12023    });
12024
12025    // Allow the LSP command to proceed. Because the buffer was edited,
12026    // the second code action will not be run.
12027    drop(lock_guard);
12028    format.await;
12029    editor.update_in(cx, |editor, window, cx| {
12030        assert_eq!(
12031            editor.text(cx),
12032            r#"
12033                applied-code-action-1-command
12034                applied-code-action-1-edit
12035                applied-formatting
12036                one
12037                two
12038                three
12039                edited
12040            "#
12041            .unindent()
12042        );
12043
12044        // The manual edit is undone first, because it is the last thing the user did
12045        // (even though the command completed afterwards).
12046        editor.undo(&Default::default(), window, cx);
12047        assert_eq!(
12048            editor.text(cx),
12049            r#"
12050                applied-code-action-1-command
12051                applied-code-action-1-edit
12052                applied-formatting
12053                one
12054                two
12055                three
12056            "#
12057            .unindent()
12058        );
12059
12060        // All the formatting (including the command, which completed after the manual edit)
12061        // is undone together.
12062        editor.undo(&Default::default(), window, cx);
12063        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12064    });
12065}
12066
12067#[gpui::test]
12068async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12069    init_test(cx, |settings| {
12070        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
12071            Formatter::LanguageServer { name: None },
12072        ])))
12073    });
12074
12075    let fs = FakeFs::new(cx.executor());
12076    fs.insert_file(path!("/file.ts"), Default::default()).await;
12077
12078    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12079
12080    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12081    language_registry.add(Arc::new(Language::new(
12082        LanguageConfig {
12083            name: "TypeScript".into(),
12084            matcher: LanguageMatcher {
12085                path_suffixes: vec!["ts".to_string()],
12086                ..Default::default()
12087            },
12088            ..LanguageConfig::default()
12089        },
12090        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12091    )));
12092    update_test_language_settings(cx, |settings| {
12093        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12094    });
12095    let mut fake_servers = language_registry.register_fake_lsp(
12096        "TypeScript",
12097        FakeLspAdapter {
12098            capabilities: lsp::ServerCapabilities {
12099                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12100                ..Default::default()
12101            },
12102            ..Default::default()
12103        },
12104    );
12105
12106    let buffer = project
12107        .update(cx, |project, cx| {
12108            project.open_local_buffer(path!("/file.ts"), cx)
12109        })
12110        .await
12111        .unwrap();
12112
12113    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12114    let (editor, cx) = cx.add_window_view(|window, cx| {
12115        build_editor_with_project(project.clone(), buffer, window, cx)
12116    });
12117    editor.update_in(cx, |editor, window, cx| {
12118        editor.set_text(
12119            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12120            window,
12121            cx,
12122        )
12123    });
12124
12125    cx.executor().start_waiting();
12126    let fake_server = fake_servers.next().await.unwrap();
12127
12128    let format = editor
12129        .update_in(cx, |editor, window, cx| {
12130            editor.perform_code_action_kind(
12131                project.clone(),
12132                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12133                window,
12134                cx,
12135            )
12136        })
12137        .unwrap();
12138    fake_server
12139        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12140            assert_eq!(
12141                params.text_document.uri,
12142                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12143            );
12144            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12145                lsp::CodeAction {
12146                    title: "Organize Imports".to_string(),
12147                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12148                    edit: Some(lsp::WorkspaceEdit {
12149                        changes: Some(
12150                            [(
12151                                params.text_document.uri.clone(),
12152                                vec![lsp::TextEdit::new(
12153                                    lsp::Range::new(
12154                                        lsp::Position::new(1, 0),
12155                                        lsp::Position::new(2, 0),
12156                                    ),
12157                                    "".to_string(),
12158                                )],
12159                            )]
12160                            .into_iter()
12161                            .collect(),
12162                        ),
12163                        ..Default::default()
12164                    }),
12165                    ..Default::default()
12166                },
12167            )]))
12168        })
12169        .next()
12170        .await;
12171    cx.executor().start_waiting();
12172    format.await;
12173    assert_eq!(
12174        editor.update(cx, |editor, cx| editor.text(cx)),
12175        "import { a } from 'module';\n\nconst x = a;\n"
12176    );
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    // Ensure we don't lock if code action hangs.
12186    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12187        move |params, _| async move {
12188            assert_eq!(
12189                params.text_document.uri,
12190                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12191            );
12192            futures::future::pending::<()>().await;
12193            unreachable!()
12194        },
12195    );
12196    let format = editor
12197        .update_in(cx, |editor, window, cx| {
12198            editor.perform_code_action_kind(
12199                project,
12200                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12201                window,
12202                cx,
12203            )
12204        })
12205        .unwrap();
12206    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12207    cx.executor().start_waiting();
12208    format.await;
12209    assert_eq!(
12210        editor.update(cx, |editor, cx| editor.text(cx)),
12211        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12212    );
12213}
12214
12215#[gpui::test]
12216async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12217    init_test(cx, |_| {});
12218
12219    let mut cx = EditorLspTestContext::new_rust(
12220        lsp::ServerCapabilities {
12221            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12222            ..Default::default()
12223        },
12224        cx,
12225    )
12226    .await;
12227
12228    cx.set_state(indoc! {"
12229        one.twoˇ
12230    "});
12231
12232    // The format request takes a long time. When it completes, it inserts
12233    // a newline and an indent before the `.`
12234    cx.lsp
12235        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12236            let executor = cx.background_executor().clone();
12237            async move {
12238                executor.timer(Duration::from_millis(100)).await;
12239                Ok(Some(vec![lsp::TextEdit {
12240                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12241                    new_text: "\n    ".into(),
12242                }]))
12243            }
12244        });
12245
12246    // Submit a format request.
12247    let format_1 = cx
12248        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12249        .unwrap();
12250    cx.executor().run_until_parked();
12251
12252    // Submit a second format request.
12253    let format_2 = cx
12254        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12255        .unwrap();
12256    cx.executor().run_until_parked();
12257
12258    // Wait for both format requests to complete
12259    cx.executor().advance_clock(Duration::from_millis(200));
12260    cx.executor().start_waiting();
12261    format_1.await.unwrap();
12262    cx.executor().start_waiting();
12263    format_2.await.unwrap();
12264
12265    // The formatting edits only happens once.
12266    cx.assert_editor_state(indoc! {"
12267        one
12268            .twoˇ
12269    "});
12270}
12271
12272#[gpui::test]
12273async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12274    init_test(cx, |settings| {
12275        settings.defaults.formatter = Some(SelectedFormatter::Auto)
12276    });
12277
12278    let mut cx = EditorLspTestContext::new_rust(
12279        lsp::ServerCapabilities {
12280            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12281            ..Default::default()
12282        },
12283        cx,
12284    )
12285    .await;
12286
12287    // Set up a buffer white some trailing whitespace and no trailing newline.
12288    cx.set_state(
12289        &[
12290            "one ",   //
12291            "twoˇ",   //
12292            "three ", //
12293            "four",   //
12294        ]
12295        .join("\n"),
12296    );
12297
12298    // Submit a format request.
12299    let format = cx
12300        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12301        .unwrap();
12302
12303    // Record which buffer changes have been sent to the language server
12304    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12305    cx.lsp
12306        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12307            let buffer_changes = buffer_changes.clone();
12308            move |params, _| {
12309                buffer_changes.lock().extend(
12310                    params
12311                        .content_changes
12312                        .into_iter()
12313                        .map(|e| (e.range.unwrap(), e.text)),
12314                );
12315            }
12316        });
12317
12318    // Handle formatting requests to the language server.
12319    cx.lsp
12320        .set_request_handler::<lsp::request::Formatting, _, _>({
12321            let buffer_changes = buffer_changes.clone();
12322            move |_, _| {
12323                // When formatting is requested, trailing whitespace has already been stripped,
12324                // and the trailing newline has already been added.
12325                assert_eq!(
12326                    &buffer_changes.lock()[1..],
12327                    &[
12328                        (
12329                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12330                            "".into()
12331                        ),
12332                        (
12333                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12334                            "".into()
12335                        ),
12336                        (
12337                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12338                            "\n".into()
12339                        ),
12340                    ]
12341                );
12342
12343                // Insert blank lines between each line of the buffer.
12344                async move {
12345                    Ok(Some(vec![
12346                        lsp::TextEdit {
12347                            range: lsp::Range::new(
12348                                lsp::Position::new(1, 0),
12349                                lsp::Position::new(1, 0),
12350                            ),
12351                            new_text: "\n".into(),
12352                        },
12353                        lsp::TextEdit {
12354                            range: lsp::Range::new(
12355                                lsp::Position::new(2, 0),
12356                                lsp::Position::new(2, 0),
12357                            ),
12358                            new_text: "\n".into(),
12359                        },
12360                    ]))
12361                }
12362            }
12363        });
12364
12365    // After formatting the buffer, the trailing whitespace is stripped,
12366    // a newline is appended, and the edits provided by the language server
12367    // have been applied.
12368    format.await.unwrap();
12369    cx.assert_editor_state(
12370        &[
12371            "one",   //
12372            "",      //
12373            "twoˇ",  //
12374            "",      //
12375            "three", //
12376            "four",  //
12377            "",      //
12378        ]
12379        .join("\n"),
12380    );
12381
12382    // Undoing the formatting undoes the trailing whitespace removal, the
12383    // trailing newline, and the LSP edits.
12384    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12385    cx.assert_editor_state(
12386        &[
12387            "one ",   //
12388            "twoˇ",   //
12389            "three ", //
12390            "four",   //
12391        ]
12392        .join("\n"),
12393    );
12394}
12395
12396#[gpui::test]
12397async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12398    cx: &mut TestAppContext,
12399) {
12400    init_test(cx, |_| {});
12401
12402    cx.update(|cx| {
12403        cx.update_global::<SettingsStore, _>(|settings, cx| {
12404            settings.update_user_settings(cx, |settings| {
12405                settings.editor.auto_signature_help = Some(true);
12406            });
12407        });
12408    });
12409
12410    let mut cx = EditorLspTestContext::new_rust(
12411        lsp::ServerCapabilities {
12412            signature_help_provider: Some(lsp::SignatureHelpOptions {
12413                ..Default::default()
12414            }),
12415            ..Default::default()
12416        },
12417        cx,
12418    )
12419    .await;
12420
12421    let language = Language::new(
12422        LanguageConfig {
12423            name: "Rust".into(),
12424            brackets: BracketPairConfig {
12425                pairs: vec![
12426                    BracketPair {
12427                        start: "{".to_string(),
12428                        end: "}".to_string(),
12429                        close: true,
12430                        surround: true,
12431                        newline: true,
12432                    },
12433                    BracketPair {
12434                        start: "(".to_string(),
12435                        end: ")".to_string(),
12436                        close: true,
12437                        surround: true,
12438                        newline: true,
12439                    },
12440                    BracketPair {
12441                        start: "/*".to_string(),
12442                        end: " */".to_string(),
12443                        close: true,
12444                        surround: true,
12445                        newline: true,
12446                    },
12447                    BracketPair {
12448                        start: "[".to_string(),
12449                        end: "]".to_string(),
12450                        close: false,
12451                        surround: false,
12452                        newline: true,
12453                    },
12454                    BracketPair {
12455                        start: "\"".to_string(),
12456                        end: "\"".to_string(),
12457                        close: true,
12458                        surround: true,
12459                        newline: false,
12460                    },
12461                    BracketPair {
12462                        start: "<".to_string(),
12463                        end: ">".to_string(),
12464                        close: false,
12465                        surround: true,
12466                        newline: true,
12467                    },
12468                ],
12469                ..Default::default()
12470            },
12471            autoclose_before: "})]".to_string(),
12472            ..Default::default()
12473        },
12474        Some(tree_sitter_rust::LANGUAGE.into()),
12475    );
12476    let language = Arc::new(language);
12477
12478    cx.language_registry().add(language.clone());
12479    cx.update_buffer(|buffer, cx| {
12480        buffer.set_language(Some(language), cx);
12481    });
12482
12483    cx.set_state(
12484        &r#"
12485            fn main() {
12486                sampleˇ
12487            }
12488        "#
12489        .unindent(),
12490    );
12491
12492    cx.update_editor(|editor, window, cx| {
12493        editor.handle_input("(", window, cx);
12494    });
12495    cx.assert_editor_state(
12496        &"
12497            fn main() {
12498                sample(ˇ)
12499            }
12500        "
12501        .unindent(),
12502    );
12503
12504    let mocked_response = lsp::SignatureHelp {
12505        signatures: vec![lsp::SignatureInformation {
12506            label: "fn sample(param1: u8, param2: u8)".to_string(),
12507            documentation: None,
12508            parameters: Some(vec![
12509                lsp::ParameterInformation {
12510                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12511                    documentation: None,
12512                },
12513                lsp::ParameterInformation {
12514                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12515                    documentation: None,
12516                },
12517            ]),
12518            active_parameter: None,
12519        }],
12520        active_signature: Some(0),
12521        active_parameter: Some(0),
12522    };
12523    handle_signature_help_request(&mut cx, mocked_response).await;
12524
12525    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12526        .await;
12527
12528    cx.editor(|editor, _, _| {
12529        let signature_help_state = editor.signature_help_state.popover().cloned();
12530        let signature = signature_help_state.unwrap();
12531        assert_eq!(
12532            signature.signatures[signature.current_signature].label,
12533            "fn sample(param1: u8, param2: u8)"
12534        );
12535    });
12536}
12537
12538#[gpui::test]
12539async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12540    init_test(cx, |_| {});
12541
12542    cx.update(|cx| {
12543        cx.update_global::<SettingsStore, _>(|settings, cx| {
12544            settings.update_user_settings(cx, |settings| {
12545                settings.editor.auto_signature_help = Some(false);
12546                settings.editor.show_signature_help_after_edits = Some(false);
12547            });
12548        });
12549    });
12550
12551    let mut cx = EditorLspTestContext::new_rust(
12552        lsp::ServerCapabilities {
12553            signature_help_provider: Some(lsp::SignatureHelpOptions {
12554                ..Default::default()
12555            }),
12556            ..Default::default()
12557        },
12558        cx,
12559    )
12560    .await;
12561
12562    let language = Language::new(
12563        LanguageConfig {
12564            name: "Rust".into(),
12565            brackets: BracketPairConfig {
12566                pairs: vec![
12567                    BracketPair {
12568                        start: "{".to_string(),
12569                        end: "}".to_string(),
12570                        close: true,
12571                        surround: true,
12572                        newline: true,
12573                    },
12574                    BracketPair {
12575                        start: "(".to_string(),
12576                        end: ")".to_string(),
12577                        close: true,
12578                        surround: true,
12579                        newline: true,
12580                    },
12581                    BracketPair {
12582                        start: "/*".to_string(),
12583                        end: " */".to_string(),
12584                        close: true,
12585                        surround: true,
12586                        newline: true,
12587                    },
12588                    BracketPair {
12589                        start: "[".to_string(),
12590                        end: "]".to_string(),
12591                        close: false,
12592                        surround: false,
12593                        newline: true,
12594                    },
12595                    BracketPair {
12596                        start: "\"".to_string(),
12597                        end: "\"".to_string(),
12598                        close: true,
12599                        surround: true,
12600                        newline: false,
12601                    },
12602                    BracketPair {
12603                        start: "<".to_string(),
12604                        end: ">".to_string(),
12605                        close: false,
12606                        surround: true,
12607                        newline: true,
12608                    },
12609                ],
12610                ..Default::default()
12611            },
12612            autoclose_before: "})]".to_string(),
12613            ..Default::default()
12614        },
12615        Some(tree_sitter_rust::LANGUAGE.into()),
12616    );
12617    let language = Arc::new(language);
12618
12619    cx.language_registry().add(language.clone());
12620    cx.update_buffer(|buffer, cx| {
12621        buffer.set_language(Some(language), cx);
12622    });
12623
12624    // Ensure that signature_help is not called when no signature help is enabled.
12625    cx.set_state(
12626        &r#"
12627            fn main() {
12628                sampleˇ
12629            }
12630        "#
12631        .unindent(),
12632    );
12633    cx.update_editor(|editor, window, cx| {
12634        editor.handle_input("(", window, cx);
12635    });
12636    cx.assert_editor_state(
12637        &"
12638            fn main() {
12639                sample(ˇ)
12640            }
12641        "
12642        .unindent(),
12643    );
12644    cx.editor(|editor, _, _| {
12645        assert!(editor.signature_help_state.task().is_none());
12646    });
12647
12648    let mocked_response = lsp::SignatureHelp {
12649        signatures: vec![lsp::SignatureInformation {
12650            label: "fn sample(param1: u8, param2: u8)".to_string(),
12651            documentation: None,
12652            parameters: Some(vec![
12653                lsp::ParameterInformation {
12654                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12655                    documentation: None,
12656                },
12657                lsp::ParameterInformation {
12658                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12659                    documentation: None,
12660                },
12661            ]),
12662            active_parameter: None,
12663        }],
12664        active_signature: Some(0),
12665        active_parameter: Some(0),
12666    };
12667
12668    // Ensure that signature_help is called when enabled afte edits
12669    cx.update(|_, cx| {
12670        cx.update_global::<SettingsStore, _>(|settings, cx| {
12671            settings.update_user_settings(cx, |settings| {
12672                settings.editor.auto_signature_help = Some(false);
12673                settings.editor.show_signature_help_after_edits = Some(true);
12674            });
12675        });
12676    });
12677    cx.set_state(
12678        &r#"
12679            fn main() {
12680                sampleˇ
12681            }
12682        "#
12683        .unindent(),
12684    );
12685    cx.update_editor(|editor, window, cx| {
12686        editor.handle_input("(", window, cx);
12687    });
12688    cx.assert_editor_state(
12689        &"
12690            fn main() {
12691                sample(ˇ)
12692            }
12693        "
12694        .unindent(),
12695    );
12696    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12697    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12698        .await;
12699    cx.update_editor(|editor, _, _| {
12700        let signature_help_state = editor.signature_help_state.popover().cloned();
12701        assert!(signature_help_state.is_some());
12702        let signature = signature_help_state.unwrap();
12703        assert_eq!(
12704            signature.signatures[signature.current_signature].label,
12705            "fn sample(param1: u8, param2: u8)"
12706        );
12707        editor.signature_help_state = SignatureHelpState::default();
12708    });
12709
12710    // Ensure that signature_help is called when auto signature help override is enabled
12711    cx.update(|_, cx| {
12712        cx.update_global::<SettingsStore, _>(|settings, cx| {
12713            settings.update_user_settings(cx, |settings| {
12714                settings.editor.auto_signature_help = Some(true);
12715                settings.editor.show_signature_help_after_edits = Some(false);
12716            });
12717        });
12718    });
12719    cx.set_state(
12720        &r#"
12721            fn main() {
12722                sampleˇ
12723            }
12724        "#
12725        .unindent(),
12726    );
12727    cx.update_editor(|editor, window, cx| {
12728        editor.handle_input("(", window, cx);
12729    });
12730    cx.assert_editor_state(
12731        &"
12732            fn main() {
12733                sample(ˇ)
12734            }
12735        "
12736        .unindent(),
12737    );
12738    handle_signature_help_request(&mut cx, mocked_response).await;
12739    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12740        .await;
12741    cx.editor(|editor, _, _| {
12742        let signature_help_state = editor.signature_help_state.popover().cloned();
12743        assert!(signature_help_state.is_some());
12744        let signature = signature_help_state.unwrap();
12745        assert_eq!(
12746            signature.signatures[signature.current_signature].label,
12747            "fn sample(param1: u8, param2: u8)"
12748        );
12749    });
12750}
12751
12752#[gpui::test]
12753async fn test_signature_help(cx: &mut TestAppContext) {
12754    init_test(cx, |_| {});
12755    cx.update(|cx| {
12756        cx.update_global::<SettingsStore, _>(|settings, cx| {
12757            settings.update_user_settings(cx, |settings| {
12758                settings.editor.auto_signature_help = Some(true);
12759            });
12760        });
12761    });
12762
12763    let mut cx = EditorLspTestContext::new_rust(
12764        lsp::ServerCapabilities {
12765            signature_help_provider: Some(lsp::SignatureHelpOptions {
12766                ..Default::default()
12767            }),
12768            ..Default::default()
12769        },
12770        cx,
12771    )
12772    .await;
12773
12774    // A test that directly calls `show_signature_help`
12775    cx.update_editor(|editor, window, cx| {
12776        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12777    });
12778
12779    let mocked_response = lsp::SignatureHelp {
12780        signatures: vec![lsp::SignatureInformation {
12781            label: "fn sample(param1: u8, param2: u8)".to_string(),
12782            documentation: None,
12783            parameters: Some(vec![
12784                lsp::ParameterInformation {
12785                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12786                    documentation: None,
12787                },
12788                lsp::ParameterInformation {
12789                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12790                    documentation: None,
12791                },
12792            ]),
12793            active_parameter: None,
12794        }],
12795        active_signature: Some(0),
12796        active_parameter: Some(0),
12797    };
12798    handle_signature_help_request(&mut cx, mocked_response).await;
12799
12800    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12801        .await;
12802
12803    cx.editor(|editor, _, _| {
12804        let signature_help_state = editor.signature_help_state.popover().cloned();
12805        assert!(signature_help_state.is_some());
12806        let signature = signature_help_state.unwrap();
12807        assert_eq!(
12808            signature.signatures[signature.current_signature].label,
12809            "fn sample(param1: u8, param2: u8)"
12810        );
12811    });
12812
12813    // When exiting outside from inside the brackets, `signature_help` is closed.
12814    cx.set_state(indoc! {"
12815        fn main() {
12816            sample(ˇ);
12817        }
12818
12819        fn sample(param1: u8, param2: u8) {}
12820    "});
12821
12822    cx.update_editor(|editor, window, cx| {
12823        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12824            s.select_ranges([0..0])
12825        });
12826    });
12827
12828    let mocked_response = lsp::SignatureHelp {
12829        signatures: Vec::new(),
12830        active_signature: None,
12831        active_parameter: None,
12832    };
12833    handle_signature_help_request(&mut cx, mocked_response).await;
12834
12835    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12836        .await;
12837
12838    cx.editor(|editor, _, _| {
12839        assert!(!editor.signature_help_state.is_shown());
12840    });
12841
12842    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
12843    cx.set_state(indoc! {"
12844        fn main() {
12845            sample(ˇ);
12846        }
12847
12848        fn sample(param1: u8, param2: u8) {}
12849    "});
12850
12851    let mocked_response = lsp::SignatureHelp {
12852        signatures: vec![lsp::SignatureInformation {
12853            label: "fn sample(param1: u8, param2: u8)".to_string(),
12854            documentation: None,
12855            parameters: Some(vec![
12856                lsp::ParameterInformation {
12857                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12858                    documentation: None,
12859                },
12860                lsp::ParameterInformation {
12861                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12862                    documentation: None,
12863                },
12864            ]),
12865            active_parameter: None,
12866        }],
12867        active_signature: Some(0),
12868        active_parameter: Some(0),
12869    };
12870    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12871    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12872        .await;
12873    cx.editor(|editor, _, _| {
12874        assert!(editor.signature_help_state.is_shown());
12875    });
12876
12877    // Restore the popover with more parameter input
12878    cx.set_state(indoc! {"
12879        fn main() {
12880            sample(param1, param2ˇ);
12881        }
12882
12883        fn sample(param1: u8, param2: u8) {}
12884    "});
12885
12886    let mocked_response = lsp::SignatureHelp {
12887        signatures: vec![lsp::SignatureInformation {
12888            label: "fn sample(param1: u8, param2: u8)".to_string(),
12889            documentation: None,
12890            parameters: Some(vec![
12891                lsp::ParameterInformation {
12892                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12893                    documentation: None,
12894                },
12895                lsp::ParameterInformation {
12896                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12897                    documentation: None,
12898                },
12899            ]),
12900            active_parameter: None,
12901        }],
12902        active_signature: Some(0),
12903        active_parameter: Some(1),
12904    };
12905    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12906    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12907        .await;
12908
12909    // When selecting a range, the popover is gone.
12910    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
12911    cx.update_editor(|editor, window, cx| {
12912        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12913            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12914        })
12915    });
12916    cx.assert_editor_state(indoc! {"
12917        fn main() {
12918            sample(param1, «ˇparam2»);
12919        }
12920
12921        fn sample(param1: u8, param2: u8) {}
12922    "});
12923    cx.editor(|editor, _, _| {
12924        assert!(!editor.signature_help_state.is_shown());
12925    });
12926
12927    // When unselecting again, the popover is back if within the brackets.
12928    cx.update_editor(|editor, window, cx| {
12929        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12930            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12931        })
12932    });
12933    cx.assert_editor_state(indoc! {"
12934        fn main() {
12935            sample(param1, ˇparam2);
12936        }
12937
12938        fn sample(param1: u8, param2: u8) {}
12939    "});
12940    handle_signature_help_request(&mut cx, mocked_response).await;
12941    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12942        .await;
12943    cx.editor(|editor, _, _| {
12944        assert!(editor.signature_help_state.is_shown());
12945    });
12946
12947    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
12948    cx.update_editor(|editor, window, cx| {
12949        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12950            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
12951            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12952        })
12953    });
12954    cx.assert_editor_state(indoc! {"
12955        fn main() {
12956            sample(param1, ˇparam2);
12957        }
12958
12959        fn sample(param1: u8, param2: u8) {}
12960    "});
12961
12962    let mocked_response = lsp::SignatureHelp {
12963        signatures: vec![lsp::SignatureInformation {
12964            label: "fn sample(param1: u8, param2: u8)".to_string(),
12965            documentation: None,
12966            parameters: Some(vec![
12967                lsp::ParameterInformation {
12968                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12969                    documentation: None,
12970                },
12971                lsp::ParameterInformation {
12972                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12973                    documentation: None,
12974                },
12975            ]),
12976            active_parameter: None,
12977        }],
12978        active_signature: Some(0),
12979        active_parameter: Some(1),
12980    };
12981    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12982    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12983        .await;
12984    cx.update_editor(|editor, _, cx| {
12985        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
12986    });
12987    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12988        .await;
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, 25)..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    cx.update_editor(|editor, window, cx| {
13002        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13003            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13004        })
13005    });
13006    cx.assert_editor_state(indoc! {"
13007        fn main() {
13008            sample(param1, ˇparam2);
13009        }
13010
13011        fn sample(param1: u8, param2: u8) {}
13012    "});
13013    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13014        .await;
13015}
13016
13017#[gpui::test]
13018async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13019    init_test(cx, |_| {});
13020
13021    let mut cx = EditorLspTestContext::new_rust(
13022        lsp::ServerCapabilities {
13023            signature_help_provider: Some(lsp::SignatureHelpOptions {
13024                ..Default::default()
13025            }),
13026            ..Default::default()
13027        },
13028        cx,
13029    )
13030    .await;
13031
13032    cx.set_state(indoc! {"
13033        fn main() {
13034            overloadedˇ
13035        }
13036    "});
13037
13038    cx.update_editor(|editor, window, cx| {
13039        editor.handle_input("(", window, cx);
13040        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13041    });
13042
13043    // Mock response with 3 signatures
13044    let mocked_response = lsp::SignatureHelp {
13045        signatures: vec![
13046            lsp::SignatureInformation {
13047                label: "fn overloaded(x: i32)".to_string(),
13048                documentation: None,
13049                parameters: Some(vec![lsp::ParameterInformation {
13050                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13051                    documentation: None,
13052                }]),
13053                active_parameter: None,
13054            },
13055            lsp::SignatureInformation {
13056                label: "fn overloaded(x: i32, y: i32)".to_string(),
13057                documentation: None,
13058                parameters: Some(vec![
13059                    lsp::ParameterInformation {
13060                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13061                        documentation: None,
13062                    },
13063                    lsp::ParameterInformation {
13064                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13065                        documentation: None,
13066                    },
13067                ]),
13068                active_parameter: None,
13069            },
13070            lsp::SignatureInformation {
13071                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13072                documentation: None,
13073                parameters: Some(vec![
13074                    lsp::ParameterInformation {
13075                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13076                        documentation: None,
13077                    },
13078                    lsp::ParameterInformation {
13079                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13080                        documentation: None,
13081                    },
13082                    lsp::ParameterInformation {
13083                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13084                        documentation: None,
13085                    },
13086                ]),
13087                active_parameter: None,
13088            },
13089        ],
13090        active_signature: Some(1),
13091        active_parameter: Some(0),
13092    };
13093    handle_signature_help_request(&mut cx, mocked_response).await;
13094
13095    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13096        .await;
13097
13098    // Verify we have multiple signatures and the right one is selected
13099    cx.editor(|editor, _, _| {
13100        let popover = editor.signature_help_state.popover().cloned().unwrap();
13101        assert_eq!(popover.signatures.len(), 3);
13102        // active_signature was 1, so that should be the current
13103        assert_eq!(popover.current_signature, 1);
13104        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13105        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13106        assert_eq!(
13107            popover.signatures[2].label,
13108            "fn overloaded(x: i32, y: i32, z: i32)"
13109        );
13110    });
13111
13112    // Test navigation functionality
13113    cx.update_editor(|editor, window, cx| {
13114        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13115    });
13116
13117    cx.editor(|editor, _, _| {
13118        let popover = editor.signature_help_state.popover().cloned().unwrap();
13119        assert_eq!(popover.current_signature, 2);
13120    });
13121
13122    // Test wrap around
13123    cx.update_editor(|editor, window, cx| {
13124        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13125    });
13126
13127    cx.editor(|editor, _, _| {
13128        let popover = editor.signature_help_state.popover().cloned().unwrap();
13129        assert_eq!(popover.current_signature, 0);
13130    });
13131
13132    // Test previous navigation
13133    cx.update_editor(|editor, window, cx| {
13134        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13135    });
13136
13137    cx.editor(|editor, _, _| {
13138        let popover = editor.signature_help_state.popover().cloned().unwrap();
13139        assert_eq!(popover.current_signature, 2);
13140    });
13141}
13142
13143#[gpui::test]
13144async fn test_completion_mode(cx: &mut TestAppContext) {
13145    init_test(cx, |_| {});
13146    let mut cx = EditorLspTestContext::new_rust(
13147        lsp::ServerCapabilities {
13148            completion_provider: Some(lsp::CompletionOptions {
13149                resolve_provider: Some(true),
13150                ..Default::default()
13151            }),
13152            ..Default::default()
13153        },
13154        cx,
13155    )
13156    .await;
13157
13158    struct Run {
13159        run_description: &'static str,
13160        initial_state: String,
13161        buffer_marked_text: String,
13162        completion_label: &'static str,
13163        completion_text: &'static str,
13164        expected_with_insert_mode: String,
13165        expected_with_replace_mode: String,
13166        expected_with_replace_subsequence_mode: String,
13167        expected_with_replace_suffix_mode: String,
13168    }
13169
13170    let runs = [
13171        Run {
13172            run_description: "Start of word matches completion text",
13173            initial_state: "before ediˇ after".into(),
13174            buffer_marked_text: "before <edi|> after".into(),
13175            completion_label: "editor",
13176            completion_text: "editor",
13177            expected_with_insert_mode: "before editorˇ after".into(),
13178            expected_with_replace_mode: "before editorˇ after".into(),
13179            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13180            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13181        },
13182        Run {
13183            run_description: "Accept same text at the middle of the word",
13184            initial_state: "before ediˇtor after".into(),
13185            buffer_marked_text: "before <edi|tor> after".into(),
13186            completion_label: "editor",
13187            completion_text: "editor",
13188            expected_with_insert_mode: "before editorˇtor after".into(),
13189            expected_with_replace_mode: "before editorˇ after".into(),
13190            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13191            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13192        },
13193        Run {
13194            run_description: "End of word matches completion text -- cursor at end",
13195            initial_state: "before torˇ after".into(),
13196            buffer_marked_text: "before <tor|> after".into(),
13197            completion_label: "editor",
13198            completion_text: "editor",
13199            expected_with_insert_mode: "before editorˇ after".into(),
13200            expected_with_replace_mode: "before editorˇ after".into(),
13201            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13202            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13203        },
13204        Run {
13205            run_description: "End of word matches completion text -- cursor at start",
13206            initial_state: "before ˇtor after".into(),
13207            buffer_marked_text: "before <|tor> after".into(),
13208            completion_label: "editor",
13209            completion_text: "editor",
13210            expected_with_insert_mode: "before editorˇtor after".into(),
13211            expected_with_replace_mode: "before editorˇ after".into(),
13212            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13213            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13214        },
13215        Run {
13216            run_description: "Prepend text containing whitespace",
13217            initial_state: "pˇfield: bool".into(),
13218            buffer_marked_text: "<p|field>: bool".into(),
13219            completion_label: "pub ",
13220            completion_text: "pub ",
13221            expected_with_insert_mode: "pub ˇfield: bool".into(),
13222            expected_with_replace_mode: "pub ˇ: bool".into(),
13223            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13224            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13225        },
13226        Run {
13227            run_description: "Add element to start of list",
13228            initial_state: "[element_ˇelement_2]".into(),
13229            buffer_marked_text: "[<element_|element_2>]".into(),
13230            completion_label: "element_1",
13231            completion_text: "element_1",
13232            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13233            expected_with_replace_mode: "[element_1ˇ]".into(),
13234            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13235            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13236        },
13237        Run {
13238            run_description: "Add element to start of list -- first and second elements are equal",
13239            initial_state: "[elˇelement]".into(),
13240            buffer_marked_text: "[<el|element>]".into(),
13241            completion_label: "element",
13242            completion_text: "element",
13243            expected_with_insert_mode: "[elementˇelement]".into(),
13244            expected_with_replace_mode: "[elementˇ]".into(),
13245            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13246            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13247        },
13248        Run {
13249            run_description: "Ends with matching suffix",
13250            initial_state: "SubˇError".into(),
13251            buffer_marked_text: "<Sub|Error>".into(),
13252            completion_label: "SubscriptionError",
13253            completion_text: "SubscriptionError",
13254            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13255            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13256            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13257            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13258        },
13259        Run {
13260            run_description: "Suffix is a subsequence -- contiguous",
13261            initial_state: "SubˇErr".into(),
13262            buffer_marked_text: "<Sub|Err>".into(),
13263            completion_label: "SubscriptionError",
13264            completion_text: "SubscriptionError",
13265            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13266            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13267            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13268            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13269        },
13270        Run {
13271            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13272            initial_state: "Suˇscrirr".into(),
13273            buffer_marked_text: "<Su|scrirr>".into(),
13274            completion_label: "SubscriptionError",
13275            completion_text: "SubscriptionError",
13276            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13277            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13278            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13279            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13280        },
13281        Run {
13282            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13283            initial_state: "foo(indˇix)".into(),
13284            buffer_marked_text: "foo(<ind|ix>)".into(),
13285            completion_label: "node_index",
13286            completion_text: "node_index",
13287            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13288            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13289            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13290            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13291        },
13292        Run {
13293            run_description: "Replace range ends before cursor - should extend to cursor",
13294            initial_state: "before editˇo after".into(),
13295            buffer_marked_text: "before <{ed}>it|o after".into(),
13296            completion_label: "editor",
13297            completion_text: "editor",
13298            expected_with_insert_mode: "before editorˇo after".into(),
13299            expected_with_replace_mode: "before editorˇo after".into(),
13300            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13301            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13302        },
13303        Run {
13304            run_description: "Uses label for suffix matching",
13305            initial_state: "before ediˇtor after".into(),
13306            buffer_marked_text: "before <edi|tor> after".into(),
13307            completion_label: "editor",
13308            completion_text: "editor()",
13309            expected_with_insert_mode: "before editor()ˇtor after".into(),
13310            expected_with_replace_mode: "before editor()ˇ after".into(),
13311            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13312            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13313        },
13314        Run {
13315            run_description: "Case insensitive subsequence and suffix matching",
13316            initial_state: "before EDiˇtoR after".into(),
13317            buffer_marked_text: "before <EDi|toR> after".into(),
13318            completion_label: "editor",
13319            completion_text: "editor",
13320            expected_with_insert_mode: "before editorˇtoR after".into(),
13321            expected_with_replace_mode: "before editorˇ after".into(),
13322            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13323            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13324        },
13325    ];
13326
13327    for run in runs {
13328        let run_variations = [
13329            (LspInsertMode::Insert, run.expected_with_insert_mode),
13330            (LspInsertMode::Replace, run.expected_with_replace_mode),
13331            (
13332                LspInsertMode::ReplaceSubsequence,
13333                run.expected_with_replace_subsequence_mode,
13334            ),
13335            (
13336                LspInsertMode::ReplaceSuffix,
13337                run.expected_with_replace_suffix_mode,
13338            ),
13339        ];
13340
13341        for (lsp_insert_mode, expected_text) in run_variations {
13342            eprintln!(
13343                "run = {:?}, mode = {lsp_insert_mode:.?}",
13344                run.run_description,
13345            );
13346
13347            update_test_language_settings(&mut cx, |settings| {
13348                settings.defaults.completions = Some(CompletionSettingsContent {
13349                    lsp_insert_mode: Some(lsp_insert_mode),
13350                    words: Some(WordsCompletionMode::Disabled),
13351                    words_min_length: Some(0),
13352                    ..Default::default()
13353                });
13354            });
13355
13356            cx.set_state(&run.initial_state);
13357            cx.update_editor(|editor, window, cx| {
13358                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13359            });
13360
13361            let counter = Arc::new(AtomicUsize::new(0));
13362            handle_completion_request_with_insert_and_replace(
13363                &mut cx,
13364                &run.buffer_marked_text,
13365                vec![(run.completion_label, run.completion_text)],
13366                counter.clone(),
13367            )
13368            .await;
13369            cx.condition(|editor, _| editor.context_menu_visible())
13370                .await;
13371            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13372
13373            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13374                editor
13375                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13376                    .unwrap()
13377            });
13378            cx.assert_editor_state(&expected_text);
13379            handle_resolve_completion_request(&mut cx, None).await;
13380            apply_additional_edits.await.unwrap();
13381        }
13382    }
13383}
13384
13385#[gpui::test]
13386async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13387    init_test(cx, |_| {});
13388    let mut cx = EditorLspTestContext::new_rust(
13389        lsp::ServerCapabilities {
13390            completion_provider: Some(lsp::CompletionOptions {
13391                resolve_provider: Some(true),
13392                ..Default::default()
13393            }),
13394            ..Default::default()
13395        },
13396        cx,
13397    )
13398    .await;
13399
13400    let initial_state = "SubˇError";
13401    let buffer_marked_text = "<Sub|Error>";
13402    let completion_text = "SubscriptionError";
13403    let expected_with_insert_mode = "SubscriptionErrorˇError";
13404    let expected_with_replace_mode = "SubscriptionErrorˇ";
13405
13406    update_test_language_settings(&mut cx, |settings| {
13407        settings.defaults.completions = Some(CompletionSettingsContent {
13408            words: Some(WordsCompletionMode::Disabled),
13409            words_min_length: Some(0),
13410            // set the opposite here to ensure that the action is overriding the default behavior
13411            lsp_insert_mode: Some(LspInsertMode::Insert),
13412            ..Default::default()
13413        });
13414    });
13415
13416    cx.set_state(initial_state);
13417    cx.update_editor(|editor, window, cx| {
13418        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13419    });
13420
13421    let counter = Arc::new(AtomicUsize::new(0));
13422    handle_completion_request_with_insert_and_replace(
13423        &mut cx,
13424        buffer_marked_text,
13425        vec![(completion_text, completion_text)],
13426        counter.clone(),
13427    )
13428    .await;
13429    cx.condition(|editor, _| editor.context_menu_visible())
13430        .await;
13431    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13432
13433    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13434        editor
13435            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13436            .unwrap()
13437    });
13438    cx.assert_editor_state(expected_with_replace_mode);
13439    handle_resolve_completion_request(&mut cx, None).await;
13440    apply_additional_edits.await.unwrap();
13441
13442    update_test_language_settings(&mut cx, |settings| {
13443        settings.defaults.completions = Some(CompletionSettingsContent {
13444            words: Some(WordsCompletionMode::Disabled),
13445            words_min_length: Some(0),
13446            // set the opposite here to ensure that the action is overriding the default behavior
13447            lsp_insert_mode: Some(LspInsertMode::Replace),
13448            ..Default::default()
13449        });
13450    });
13451
13452    cx.set_state(initial_state);
13453    cx.update_editor(|editor, window, cx| {
13454        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13455    });
13456    handle_completion_request_with_insert_and_replace(
13457        &mut cx,
13458        buffer_marked_text,
13459        vec![(completion_text, completion_text)],
13460        counter.clone(),
13461    )
13462    .await;
13463    cx.condition(|editor, _| editor.context_menu_visible())
13464        .await;
13465    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13466
13467    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13468        editor
13469            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13470            .unwrap()
13471    });
13472    cx.assert_editor_state(expected_with_insert_mode);
13473    handle_resolve_completion_request(&mut cx, None).await;
13474    apply_additional_edits.await.unwrap();
13475}
13476
13477#[gpui::test]
13478async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13479    init_test(cx, |_| {});
13480    let mut cx = EditorLspTestContext::new_rust(
13481        lsp::ServerCapabilities {
13482            completion_provider: Some(lsp::CompletionOptions {
13483                resolve_provider: Some(true),
13484                ..Default::default()
13485            }),
13486            ..Default::default()
13487        },
13488        cx,
13489    )
13490    .await;
13491
13492    // scenario: surrounding text matches completion text
13493    let completion_text = "to_offset";
13494    let initial_state = indoc! {"
13495        1. buf.to_offˇsuffix
13496        2. buf.to_offˇsuf
13497        3. buf.to_offˇfix
13498        4. buf.to_offˇ
13499        5. into_offˇensive
13500        6. ˇsuffix
13501        7. let ˇ //
13502        8. aaˇzz
13503        9. buf.to_off«zzzzzˇ»suffix
13504        10. buf.«ˇzzzzz»suffix
13505        11. to_off«ˇzzzzz»
13506
13507        buf.to_offˇsuffix  // newest cursor
13508    "};
13509    let completion_marked_buffer = indoc! {"
13510        1. buf.to_offsuffix
13511        2. buf.to_offsuf
13512        3. buf.to_offfix
13513        4. buf.to_off
13514        5. into_offensive
13515        6. suffix
13516        7. let  //
13517        8. aazz
13518        9. buf.to_offzzzzzsuffix
13519        10. buf.zzzzzsuffix
13520        11. to_offzzzzz
13521
13522        buf.<to_off|suffix>  // newest cursor
13523    "};
13524    let expected = indoc! {"
13525        1. buf.to_offsetˇ
13526        2. buf.to_offsetˇsuf
13527        3. buf.to_offsetˇfix
13528        4. buf.to_offsetˇ
13529        5. into_offsetˇensive
13530        6. to_offsetˇsuffix
13531        7. let to_offsetˇ //
13532        8. aato_offsetˇzz
13533        9. buf.to_offsetˇ
13534        10. buf.to_offsetˇsuffix
13535        11. to_offsetˇ
13536
13537        buf.to_offsetˇ  // newest cursor
13538    "};
13539    cx.set_state(initial_state);
13540    cx.update_editor(|editor, window, cx| {
13541        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13542    });
13543    handle_completion_request_with_insert_and_replace(
13544        &mut cx,
13545        completion_marked_buffer,
13546        vec![(completion_text, completion_text)],
13547        Arc::new(AtomicUsize::new(0)),
13548    )
13549    .await;
13550    cx.condition(|editor, _| editor.context_menu_visible())
13551        .await;
13552    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13553        editor
13554            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13555            .unwrap()
13556    });
13557    cx.assert_editor_state(expected);
13558    handle_resolve_completion_request(&mut cx, None).await;
13559    apply_additional_edits.await.unwrap();
13560
13561    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13562    let completion_text = "foo_and_bar";
13563    let initial_state = indoc! {"
13564        1. ooanbˇ
13565        2. zooanbˇ
13566        3. ooanbˇz
13567        4. zooanbˇz
13568        5. ooanˇ
13569        6. oanbˇ
13570
13571        ooanbˇ
13572    "};
13573    let completion_marked_buffer = indoc! {"
13574        1. ooanb
13575        2. zooanb
13576        3. ooanbz
13577        4. zooanbz
13578        5. ooan
13579        6. oanb
13580
13581        <ooanb|>
13582    "};
13583    let expected = indoc! {"
13584        1. foo_and_barˇ
13585        2. zfoo_and_barˇ
13586        3. foo_and_barˇz
13587        4. zfoo_and_barˇz
13588        5. ooanfoo_and_barˇ
13589        6. oanbfoo_and_barˇ
13590
13591        foo_and_barˇ
13592    "};
13593    cx.set_state(initial_state);
13594    cx.update_editor(|editor, window, cx| {
13595        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13596    });
13597    handle_completion_request_with_insert_and_replace(
13598        &mut cx,
13599        completion_marked_buffer,
13600        vec![(completion_text, completion_text)],
13601        Arc::new(AtomicUsize::new(0)),
13602    )
13603    .await;
13604    cx.condition(|editor, _| editor.context_menu_visible())
13605        .await;
13606    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13607        editor
13608            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13609            .unwrap()
13610    });
13611    cx.assert_editor_state(expected);
13612    handle_resolve_completion_request(&mut cx, None).await;
13613    apply_additional_edits.await.unwrap();
13614
13615    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13616    // (expects the same as if it was inserted at the end)
13617    let completion_text = "foo_and_bar";
13618    let initial_state = indoc! {"
13619        1. ooˇanb
13620        2. zooˇanb
13621        3. ooˇanbz
13622        4. zooˇanbz
13623
13624        ooˇanb
13625    "};
13626    let completion_marked_buffer = indoc! {"
13627        1. ooanb
13628        2. zooanb
13629        3. ooanbz
13630        4. zooanbz
13631
13632        <oo|anb>
13633    "};
13634    let expected = indoc! {"
13635        1. foo_and_barˇ
13636        2. zfoo_and_barˇ
13637        3. foo_and_barˇz
13638        4. zfoo_and_barˇz
13639
13640        foo_and_barˇ
13641    "};
13642    cx.set_state(initial_state);
13643    cx.update_editor(|editor, window, cx| {
13644        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13645    });
13646    handle_completion_request_with_insert_and_replace(
13647        &mut cx,
13648        completion_marked_buffer,
13649        vec![(completion_text, completion_text)],
13650        Arc::new(AtomicUsize::new(0)),
13651    )
13652    .await;
13653    cx.condition(|editor, _| editor.context_menu_visible())
13654        .await;
13655    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13656        editor
13657            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13658            .unwrap()
13659    });
13660    cx.assert_editor_state(expected);
13661    handle_resolve_completion_request(&mut cx, None).await;
13662    apply_additional_edits.await.unwrap();
13663}
13664
13665// This used to crash
13666#[gpui::test]
13667async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13668    init_test(cx, |_| {});
13669
13670    let buffer_text = indoc! {"
13671        fn main() {
13672            10.satu;
13673
13674            //
13675            // separate cursors so they open in different excerpts (manually reproducible)
13676            //
13677
13678            10.satu20;
13679        }
13680    "};
13681    let multibuffer_text_with_selections = indoc! {"
13682        fn main() {
13683            10.satuˇ;
13684
13685            //
13686
13687            //
13688
13689            10.satuˇ20;
13690        }
13691    "};
13692    let expected_multibuffer = indoc! {"
13693        fn main() {
13694            10.saturating_sub()ˇ;
13695
13696            //
13697
13698            //
13699
13700            10.saturating_sub()ˇ;
13701        }
13702    "};
13703
13704    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13705    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13706
13707    let fs = FakeFs::new(cx.executor());
13708    fs.insert_tree(
13709        path!("/a"),
13710        json!({
13711            "main.rs": buffer_text,
13712        }),
13713    )
13714    .await;
13715
13716    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13717    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13718    language_registry.add(rust_lang());
13719    let mut fake_servers = language_registry.register_fake_lsp(
13720        "Rust",
13721        FakeLspAdapter {
13722            capabilities: lsp::ServerCapabilities {
13723                completion_provider: Some(lsp::CompletionOptions {
13724                    resolve_provider: None,
13725                    ..lsp::CompletionOptions::default()
13726                }),
13727                ..lsp::ServerCapabilities::default()
13728            },
13729            ..FakeLspAdapter::default()
13730        },
13731    );
13732    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13733    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13734    let buffer = project
13735        .update(cx, |project, cx| {
13736            project.open_local_buffer(path!("/a/main.rs"), cx)
13737        })
13738        .await
13739        .unwrap();
13740
13741    let multi_buffer = cx.new(|cx| {
13742        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13743        multi_buffer.push_excerpts(
13744            buffer.clone(),
13745            [ExcerptRange::new(0..first_excerpt_end)],
13746            cx,
13747        );
13748        multi_buffer.push_excerpts(
13749            buffer.clone(),
13750            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13751            cx,
13752        );
13753        multi_buffer
13754    });
13755
13756    let editor = workspace
13757        .update(cx, |_, window, cx| {
13758            cx.new(|cx| {
13759                Editor::new(
13760                    EditorMode::Full {
13761                        scale_ui_elements_with_buffer_font_size: false,
13762                        show_active_line_background: false,
13763                        sized_by_content: false,
13764                    },
13765                    multi_buffer.clone(),
13766                    Some(project.clone()),
13767                    window,
13768                    cx,
13769                )
13770            })
13771        })
13772        .unwrap();
13773
13774    let pane = workspace
13775        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13776        .unwrap();
13777    pane.update_in(cx, |pane, window, cx| {
13778        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13779    });
13780
13781    let fake_server = fake_servers.next().await.unwrap();
13782
13783    editor.update_in(cx, |editor, window, cx| {
13784        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13785            s.select_ranges([
13786                Point::new(1, 11)..Point::new(1, 11),
13787                Point::new(7, 11)..Point::new(7, 11),
13788            ])
13789        });
13790
13791        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13792    });
13793
13794    editor.update_in(cx, |editor, window, cx| {
13795        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13796    });
13797
13798    fake_server
13799        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13800            let completion_item = lsp::CompletionItem {
13801                label: "saturating_sub()".into(),
13802                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13803                    lsp::InsertReplaceEdit {
13804                        new_text: "saturating_sub()".to_owned(),
13805                        insert: lsp::Range::new(
13806                            lsp::Position::new(7, 7),
13807                            lsp::Position::new(7, 11),
13808                        ),
13809                        replace: lsp::Range::new(
13810                            lsp::Position::new(7, 7),
13811                            lsp::Position::new(7, 13),
13812                        ),
13813                    },
13814                )),
13815                ..lsp::CompletionItem::default()
13816            };
13817
13818            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13819        })
13820        .next()
13821        .await
13822        .unwrap();
13823
13824    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13825        .await;
13826
13827    editor
13828        .update_in(cx, |editor, window, cx| {
13829            editor
13830                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13831                .unwrap()
13832        })
13833        .await
13834        .unwrap();
13835
13836    editor.update(cx, |editor, cx| {
13837        assert_text_with_selections(editor, expected_multibuffer, cx);
13838    })
13839}
13840
13841#[gpui::test]
13842async fn test_completion(cx: &mut TestAppContext) {
13843    init_test(cx, |_| {});
13844
13845    let mut cx = EditorLspTestContext::new_rust(
13846        lsp::ServerCapabilities {
13847            completion_provider: Some(lsp::CompletionOptions {
13848                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13849                resolve_provider: Some(true),
13850                ..Default::default()
13851            }),
13852            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13853            ..Default::default()
13854        },
13855        cx,
13856    )
13857    .await;
13858    let counter = Arc::new(AtomicUsize::new(0));
13859
13860    cx.set_state(indoc! {"
13861        oneˇ
13862        two
13863        three
13864    "});
13865    cx.simulate_keystroke(".");
13866    handle_completion_request(
13867        indoc! {"
13868            one.|<>
13869            two
13870            three
13871        "},
13872        vec!["first_completion", "second_completion"],
13873        true,
13874        counter.clone(),
13875        &mut cx,
13876    )
13877    .await;
13878    cx.condition(|editor, _| editor.context_menu_visible())
13879        .await;
13880    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13881
13882    let _handler = handle_signature_help_request(
13883        &mut cx,
13884        lsp::SignatureHelp {
13885            signatures: vec![lsp::SignatureInformation {
13886                label: "test signature".to_string(),
13887                documentation: None,
13888                parameters: Some(vec![lsp::ParameterInformation {
13889                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
13890                    documentation: None,
13891                }]),
13892                active_parameter: None,
13893            }],
13894            active_signature: None,
13895            active_parameter: None,
13896        },
13897    );
13898    cx.update_editor(|editor, window, cx| {
13899        assert!(
13900            !editor.signature_help_state.is_shown(),
13901            "No signature help was called for"
13902        );
13903        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13904    });
13905    cx.run_until_parked();
13906    cx.update_editor(|editor, _, _| {
13907        assert!(
13908            !editor.signature_help_state.is_shown(),
13909            "No signature help should be shown when completions menu is open"
13910        );
13911    });
13912
13913    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13914        editor.context_menu_next(&Default::default(), window, cx);
13915        editor
13916            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13917            .unwrap()
13918    });
13919    cx.assert_editor_state(indoc! {"
13920        one.second_completionˇ
13921        two
13922        three
13923    "});
13924
13925    handle_resolve_completion_request(
13926        &mut cx,
13927        Some(vec![
13928            (
13929                //This overlaps with the primary completion edit which is
13930                //misbehavior from the LSP spec, test that we filter it out
13931                indoc! {"
13932                    one.second_ˇcompletion
13933                    two
13934                    threeˇ
13935                "},
13936                "overlapping additional edit",
13937            ),
13938            (
13939                indoc! {"
13940                    one.second_completion
13941                    two
13942                    threeˇ
13943                "},
13944                "\nadditional edit",
13945            ),
13946        ]),
13947    )
13948    .await;
13949    apply_additional_edits.await.unwrap();
13950    cx.assert_editor_state(indoc! {"
13951        one.second_completionˇ
13952        two
13953        three
13954        additional edit
13955    "});
13956
13957    cx.set_state(indoc! {"
13958        one.second_completion
13959        twoˇ
13960        threeˇ
13961        additional edit
13962    "});
13963    cx.simulate_keystroke(" ");
13964    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13965    cx.simulate_keystroke("s");
13966    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13967
13968    cx.assert_editor_state(indoc! {"
13969        one.second_completion
13970        two sˇ
13971        three sˇ
13972        additional edit
13973    "});
13974    handle_completion_request(
13975        indoc! {"
13976            one.second_completion
13977            two s
13978            three <s|>
13979            additional edit
13980        "},
13981        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
13982        true,
13983        counter.clone(),
13984        &mut cx,
13985    )
13986    .await;
13987    cx.condition(|editor, _| editor.context_menu_visible())
13988        .await;
13989    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13990
13991    cx.simulate_keystroke("i");
13992
13993    handle_completion_request(
13994        indoc! {"
13995            one.second_completion
13996            two si
13997            three <si|>
13998            additional edit
13999        "},
14000        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14001        true,
14002        counter.clone(),
14003        &mut cx,
14004    )
14005    .await;
14006    cx.condition(|editor, _| editor.context_menu_visible())
14007        .await;
14008    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14009
14010    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14011        editor
14012            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14013            .unwrap()
14014    });
14015    cx.assert_editor_state(indoc! {"
14016        one.second_completion
14017        two sixth_completionˇ
14018        three sixth_completionˇ
14019        additional edit
14020    "});
14021
14022    apply_additional_edits.await.unwrap();
14023
14024    update_test_language_settings(&mut cx, |settings| {
14025        settings.defaults.show_completions_on_input = Some(false);
14026    });
14027    cx.set_state("editorˇ");
14028    cx.simulate_keystroke(".");
14029    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14030    cx.simulate_keystrokes("c l o");
14031    cx.assert_editor_state("editor.cloˇ");
14032    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14033    cx.update_editor(|editor, window, cx| {
14034        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14035    });
14036    handle_completion_request(
14037        "editor.<clo|>",
14038        vec!["close", "clobber"],
14039        true,
14040        counter.clone(),
14041        &mut cx,
14042    )
14043    .await;
14044    cx.condition(|editor, _| editor.context_menu_visible())
14045        .await;
14046    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14047
14048    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14049        editor
14050            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14051            .unwrap()
14052    });
14053    cx.assert_editor_state("editor.clobberˇ");
14054    handle_resolve_completion_request(&mut cx, None).await;
14055    apply_additional_edits.await.unwrap();
14056}
14057
14058#[gpui::test]
14059async fn test_completion_reuse(cx: &mut TestAppContext) {
14060    init_test(cx, |_| {});
14061
14062    let mut cx = EditorLspTestContext::new_rust(
14063        lsp::ServerCapabilities {
14064            completion_provider: Some(lsp::CompletionOptions {
14065                trigger_characters: Some(vec![".".to_string()]),
14066                ..Default::default()
14067            }),
14068            ..Default::default()
14069        },
14070        cx,
14071    )
14072    .await;
14073
14074    let counter = Arc::new(AtomicUsize::new(0));
14075    cx.set_state("objˇ");
14076    cx.simulate_keystroke(".");
14077
14078    // Initial completion request returns complete results
14079    let is_incomplete = false;
14080    handle_completion_request(
14081        "obj.|<>",
14082        vec!["a", "ab", "abc"],
14083        is_incomplete,
14084        counter.clone(),
14085        &mut cx,
14086    )
14087    .await;
14088    cx.run_until_parked();
14089    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14090    cx.assert_editor_state("obj.ˇ");
14091    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14092
14093    // Type "a" - filters existing completions
14094    cx.simulate_keystroke("a");
14095    cx.run_until_parked();
14096    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14097    cx.assert_editor_state("obj.aˇ");
14098    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14099
14100    // Type "b" - filters existing completions
14101    cx.simulate_keystroke("b");
14102    cx.run_until_parked();
14103    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14104    cx.assert_editor_state("obj.abˇ");
14105    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14106
14107    // Type "c" - filters existing completions
14108    cx.simulate_keystroke("c");
14109    cx.run_until_parked();
14110    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14111    cx.assert_editor_state("obj.abcˇ");
14112    check_displayed_completions(vec!["abc"], &mut cx);
14113
14114    // Backspace to delete "c" - filters existing completions
14115    cx.update_editor(|editor, window, cx| {
14116        editor.backspace(&Backspace, window, cx);
14117    });
14118    cx.run_until_parked();
14119    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14120    cx.assert_editor_state("obj.abˇ");
14121    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14122
14123    // Moving cursor to the left dismisses menu.
14124    cx.update_editor(|editor, window, cx| {
14125        editor.move_left(&MoveLeft, window, cx);
14126    });
14127    cx.run_until_parked();
14128    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14129    cx.assert_editor_state("obj.aˇb");
14130    cx.update_editor(|editor, _, _| {
14131        assert_eq!(editor.context_menu_visible(), false);
14132    });
14133
14134    // Type "b" - new request
14135    cx.simulate_keystroke("b");
14136    let is_incomplete = false;
14137    handle_completion_request(
14138        "obj.<ab|>a",
14139        vec!["ab", "abc"],
14140        is_incomplete,
14141        counter.clone(),
14142        &mut cx,
14143    )
14144    .await;
14145    cx.run_until_parked();
14146    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14147    cx.assert_editor_state("obj.abˇb");
14148    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14149
14150    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14151    cx.update_editor(|editor, window, cx| {
14152        editor.backspace(&Backspace, window, cx);
14153    });
14154    let is_incomplete = false;
14155    handle_completion_request(
14156        "obj.<a|>b",
14157        vec!["a", "ab", "abc"],
14158        is_incomplete,
14159        counter.clone(),
14160        &mut cx,
14161    )
14162    .await;
14163    cx.run_until_parked();
14164    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14165    cx.assert_editor_state("obj.aˇb");
14166    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14167
14168    // Backspace to delete "a" - dismisses menu.
14169    cx.update_editor(|editor, window, cx| {
14170        editor.backspace(&Backspace, window, cx);
14171    });
14172    cx.run_until_parked();
14173    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14174    cx.assert_editor_state("obj.ˇb");
14175    cx.update_editor(|editor, _, _| {
14176        assert_eq!(editor.context_menu_visible(), false);
14177    });
14178}
14179
14180#[gpui::test]
14181async fn test_word_completion(cx: &mut TestAppContext) {
14182    let lsp_fetch_timeout_ms = 10;
14183    init_test(cx, |language_settings| {
14184        language_settings.defaults.completions = Some(CompletionSettingsContent {
14185            words_min_length: Some(0),
14186            lsp_fetch_timeout_ms: Some(10),
14187            lsp_insert_mode: Some(LspInsertMode::Insert),
14188            ..Default::default()
14189        });
14190    });
14191
14192    let mut cx = EditorLspTestContext::new_rust(
14193        lsp::ServerCapabilities {
14194            completion_provider: Some(lsp::CompletionOptions {
14195                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14196                ..lsp::CompletionOptions::default()
14197            }),
14198            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14199            ..lsp::ServerCapabilities::default()
14200        },
14201        cx,
14202    )
14203    .await;
14204
14205    let throttle_completions = Arc::new(AtomicBool::new(false));
14206
14207    let lsp_throttle_completions = throttle_completions.clone();
14208    let _completion_requests_handler =
14209        cx.lsp
14210            .server
14211            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14212                let lsp_throttle_completions = lsp_throttle_completions.clone();
14213                let cx = cx.clone();
14214                async move {
14215                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14216                        cx.background_executor()
14217                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14218                            .await;
14219                    }
14220                    Ok(Some(lsp::CompletionResponse::Array(vec![
14221                        lsp::CompletionItem {
14222                            label: "first".into(),
14223                            ..lsp::CompletionItem::default()
14224                        },
14225                        lsp::CompletionItem {
14226                            label: "last".into(),
14227                            ..lsp::CompletionItem::default()
14228                        },
14229                    ])))
14230                }
14231            });
14232
14233    cx.set_state(indoc! {"
14234        oneˇ
14235        two
14236        three
14237    "});
14238    cx.simulate_keystroke(".");
14239    cx.executor().run_until_parked();
14240    cx.condition(|editor, _| editor.context_menu_visible())
14241        .await;
14242    cx.update_editor(|editor, window, cx| {
14243        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14244        {
14245            assert_eq!(
14246                completion_menu_entries(menu),
14247                &["first", "last"],
14248                "When LSP server is fast to reply, no fallback word completions are used"
14249            );
14250        } else {
14251            panic!("expected completion menu to be open");
14252        }
14253        editor.cancel(&Cancel, window, cx);
14254    });
14255    cx.executor().run_until_parked();
14256    cx.condition(|editor, _| !editor.context_menu_visible())
14257        .await;
14258
14259    throttle_completions.store(true, atomic::Ordering::Release);
14260    cx.simulate_keystroke(".");
14261    cx.executor()
14262        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14263    cx.executor().run_until_parked();
14264    cx.condition(|editor, _| editor.context_menu_visible())
14265        .await;
14266    cx.update_editor(|editor, _, _| {
14267        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14268        {
14269            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14270                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14271        } else {
14272            panic!("expected completion menu to be open");
14273        }
14274    });
14275}
14276
14277#[gpui::test]
14278async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14279    init_test(cx, |language_settings| {
14280        language_settings.defaults.completions = Some(CompletionSettingsContent {
14281            words: Some(WordsCompletionMode::Enabled),
14282            words_min_length: Some(0),
14283            lsp_insert_mode: Some(LspInsertMode::Insert),
14284            ..Default::default()
14285        });
14286    });
14287
14288    let mut cx = EditorLspTestContext::new_rust(
14289        lsp::ServerCapabilities {
14290            completion_provider: Some(lsp::CompletionOptions {
14291                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14292                ..lsp::CompletionOptions::default()
14293            }),
14294            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14295            ..lsp::ServerCapabilities::default()
14296        },
14297        cx,
14298    )
14299    .await;
14300
14301    let _completion_requests_handler =
14302        cx.lsp
14303            .server
14304            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14305                Ok(Some(lsp::CompletionResponse::Array(vec![
14306                    lsp::CompletionItem {
14307                        label: "first".into(),
14308                        ..lsp::CompletionItem::default()
14309                    },
14310                    lsp::CompletionItem {
14311                        label: "last".into(),
14312                        ..lsp::CompletionItem::default()
14313                    },
14314                ])))
14315            });
14316
14317    cx.set_state(indoc! {"ˇ
14318        first
14319        last
14320        second
14321    "});
14322    cx.simulate_keystroke(".");
14323    cx.executor().run_until_parked();
14324    cx.condition(|editor, _| editor.context_menu_visible())
14325        .await;
14326    cx.update_editor(|editor, _, _| {
14327        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14328        {
14329            assert_eq!(
14330                completion_menu_entries(menu),
14331                &["first", "last", "second"],
14332                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14333            );
14334        } else {
14335            panic!("expected completion menu to be open");
14336        }
14337    });
14338}
14339
14340#[gpui::test]
14341async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14342    init_test(cx, |language_settings| {
14343        language_settings.defaults.completions = Some(CompletionSettingsContent {
14344            words: Some(WordsCompletionMode::Disabled),
14345            words_min_length: Some(0),
14346            lsp_insert_mode: Some(LspInsertMode::Insert),
14347            ..Default::default()
14348        });
14349    });
14350
14351    let mut cx = EditorLspTestContext::new_rust(
14352        lsp::ServerCapabilities {
14353            completion_provider: Some(lsp::CompletionOptions {
14354                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14355                ..lsp::CompletionOptions::default()
14356            }),
14357            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14358            ..lsp::ServerCapabilities::default()
14359        },
14360        cx,
14361    )
14362    .await;
14363
14364    let _completion_requests_handler =
14365        cx.lsp
14366            .server
14367            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14368                panic!("LSP completions should not be queried when dealing with word completions")
14369            });
14370
14371    cx.set_state(indoc! {"ˇ
14372        first
14373        last
14374        second
14375    "});
14376    cx.update_editor(|editor, window, cx| {
14377        editor.show_word_completions(&ShowWordCompletions, window, cx);
14378    });
14379    cx.executor().run_until_parked();
14380    cx.condition(|editor, _| editor.context_menu_visible())
14381        .await;
14382    cx.update_editor(|editor, _, _| {
14383        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14384        {
14385            assert_eq!(
14386                completion_menu_entries(menu),
14387                &["first", "last", "second"],
14388                "`ShowWordCompletions` action should show word completions"
14389            );
14390        } else {
14391            panic!("expected completion menu to be open");
14392        }
14393    });
14394
14395    cx.simulate_keystroke("l");
14396    cx.executor().run_until_parked();
14397    cx.condition(|editor, _| editor.context_menu_visible())
14398        .await;
14399    cx.update_editor(|editor, _, _| {
14400        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14401        {
14402            assert_eq!(
14403                completion_menu_entries(menu),
14404                &["last"],
14405                "After showing word completions, further editing should filter them and not query the LSP"
14406            );
14407        } else {
14408            panic!("expected completion menu to be open");
14409        }
14410    });
14411}
14412
14413#[gpui::test]
14414async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14415    init_test(cx, |language_settings| {
14416        language_settings.defaults.completions = Some(CompletionSettingsContent {
14417            words_min_length: Some(0),
14418            lsp: Some(false),
14419            lsp_insert_mode: Some(LspInsertMode::Insert),
14420            ..Default::default()
14421        });
14422    });
14423
14424    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14425
14426    cx.set_state(indoc! {"ˇ
14427        0_usize
14428        let
14429        33
14430        4.5f32
14431    "});
14432    cx.update_editor(|editor, window, cx| {
14433        editor.show_completions(&ShowCompletions::default(), window, cx);
14434    });
14435    cx.executor().run_until_parked();
14436    cx.condition(|editor, _| editor.context_menu_visible())
14437        .await;
14438    cx.update_editor(|editor, window, cx| {
14439        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14440        {
14441            assert_eq!(
14442                completion_menu_entries(menu),
14443                &["let"],
14444                "With no digits in the completion query, no digits should be in the word completions"
14445            );
14446        } else {
14447            panic!("expected completion menu to be open");
14448        }
14449        editor.cancel(&Cancel, window, cx);
14450    });
14451
14452    cx.set_state(indoc! {"14453        0_usize
14454        let
14455        3
14456        33.35f32
14457    "});
14458    cx.update_editor(|editor, window, cx| {
14459        editor.show_completions(&ShowCompletions::default(), window, cx);
14460    });
14461    cx.executor().run_until_parked();
14462    cx.condition(|editor, _| editor.context_menu_visible())
14463        .await;
14464    cx.update_editor(|editor, _, _| {
14465        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14466        {
14467            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14468                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14469        } else {
14470            panic!("expected completion menu to be open");
14471        }
14472    });
14473}
14474
14475#[gpui::test]
14476async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14477    init_test(cx, |language_settings| {
14478        language_settings.defaults.completions = Some(CompletionSettingsContent {
14479            words: Some(WordsCompletionMode::Enabled),
14480            words_min_length: Some(3),
14481            lsp_insert_mode: Some(LspInsertMode::Insert),
14482            ..Default::default()
14483        });
14484    });
14485
14486    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14487    cx.set_state(indoc! {"ˇ
14488        wow
14489        wowen
14490        wowser
14491    "});
14492    cx.simulate_keystroke("w");
14493    cx.executor().run_until_parked();
14494    cx.update_editor(|editor, _, _| {
14495        if editor.context_menu.borrow_mut().is_some() {
14496            panic!(
14497                "expected completion menu to be hidden, as words completion threshold is not met"
14498            );
14499        }
14500    });
14501
14502    cx.update_editor(|editor, window, cx| {
14503        editor.show_word_completions(&ShowWordCompletions, window, cx);
14504    });
14505    cx.executor().run_until_parked();
14506    cx.update_editor(|editor, window, cx| {
14507        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14508        {
14509            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");
14510        } else {
14511            panic!("expected completion menu to be open after the word completions are called with an action");
14512        }
14513
14514        editor.cancel(&Cancel, window, cx);
14515    });
14516    cx.update_editor(|editor, _, _| {
14517        if editor.context_menu.borrow_mut().is_some() {
14518            panic!("expected completion menu to be hidden after canceling");
14519        }
14520    });
14521
14522    cx.simulate_keystroke("o");
14523    cx.executor().run_until_parked();
14524    cx.update_editor(|editor, _, _| {
14525        if editor.context_menu.borrow_mut().is_some() {
14526            panic!(
14527                "expected completion menu to be hidden, as words completion threshold is not met still"
14528            );
14529        }
14530    });
14531
14532    cx.simulate_keystroke("w");
14533    cx.executor().run_until_parked();
14534    cx.update_editor(|editor, _, _| {
14535        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14536        {
14537            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14538        } else {
14539            panic!("expected completion menu to be open after the word completions threshold is met");
14540        }
14541    });
14542}
14543
14544#[gpui::test]
14545async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14546    init_test(cx, |language_settings| {
14547        language_settings.defaults.completions = Some(CompletionSettingsContent {
14548            words: Some(WordsCompletionMode::Enabled),
14549            words_min_length: Some(0),
14550            lsp_insert_mode: Some(LspInsertMode::Insert),
14551            ..Default::default()
14552        });
14553    });
14554
14555    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14556    cx.update_editor(|editor, _, _| {
14557        editor.disable_word_completions();
14558    });
14559    cx.set_state(indoc! {"ˇ
14560        wow
14561        wowen
14562        wowser
14563    "});
14564    cx.simulate_keystroke("w");
14565    cx.executor().run_until_parked();
14566    cx.update_editor(|editor, _, _| {
14567        if editor.context_menu.borrow_mut().is_some() {
14568            panic!(
14569                "expected completion menu to be hidden, as words completion are disabled for this editor"
14570            );
14571        }
14572    });
14573
14574    cx.update_editor(|editor, window, cx| {
14575        editor.show_word_completions(&ShowWordCompletions, window, cx);
14576    });
14577    cx.executor().run_until_parked();
14578    cx.update_editor(|editor, _, _| {
14579        if editor.context_menu.borrow_mut().is_some() {
14580            panic!(
14581                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14582            );
14583        }
14584    });
14585}
14586
14587fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14588    let position = || lsp::Position {
14589        line: params.text_document_position.position.line,
14590        character: params.text_document_position.position.character,
14591    };
14592    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14593        range: lsp::Range {
14594            start: position(),
14595            end: position(),
14596        },
14597        new_text: text.to_string(),
14598    }))
14599}
14600
14601#[gpui::test]
14602async fn test_multiline_completion(cx: &mut TestAppContext) {
14603    init_test(cx, |_| {});
14604
14605    let fs = FakeFs::new(cx.executor());
14606    fs.insert_tree(
14607        path!("/a"),
14608        json!({
14609            "main.ts": "a",
14610        }),
14611    )
14612    .await;
14613
14614    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14615    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14616    let typescript_language = Arc::new(Language::new(
14617        LanguageConfig {
14618            name: "TypeScript".into(),
14619            matcher: LanguageMatcher {
14620                path_suffixes: vec!["ts".to_string()],
14621                ..LanguageMatcher::default()
14622            },
14623            line_comments: vec!["// ".into()],
14624            ..LanguageConfig::default()
14625        },
14626        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14627    ));
14628    language_registry.add(typescript_language.clone());
14629    let mut fake_servers = language_registry.register_fake_lsp(
14630        "TypeScript",
14631        FakeLspAdapter {
14632            capabilities: lsp::ServerCapabilities {
14633                completion_provider: Some(lsp::CompletionOptions {
14634                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14635                    ..lsp::CompletionOptions::default()
14636                }),
14637                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14638                ..lsp::ServerCapabilities::default()
14639            },
14640            // Emulate vtsls label generation
14641            label_for_completion: Some(Box::new(|item, _| {
14642                let text = if let Some(description) = item
14643                    .label_details
14644                    .as_ref()
14645                    .and_then(|label_details| label_details.description.as_ref())
14646                {
14647                    format!("{} {}", item.label, description)
14648                } else if let Some(detail) = &item.detail {
14649                    format!("{} {}", item.label, detail)
14650                } else {
14651                    item.label.clone()
14652                };
14653                let len = text.len();
14654                Some(language::CodeLabel {
14655                    text,
14656                    runs: Vec::new(),
14657                    filter_range: 0..len,
14658                })
14659            })),
14660            ..FakeLspAdapter::default()
14661        },
14662    );
14663    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14664    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14665    let worktree_id = workspace
14666        .update(cx, |workspace, _window, cx| {
14667            workspace.project().update(cx, |project, cx| {
14668                project.worktrees(cx).next().unwrap().read(cx).id()
14669            })
14670        })
14671        .unwrap();
14672    let _buffer = project
14673        .update(cx, |project, cx| {
14674            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14675        })
14676        .await
14677        .unwrap();
14678    let editor = workspace
14679        .update(cx, |workspace, window, cx| {
14680            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
14681        })
14682        .unwrap()
14683        .await
14684        .unwrap()
14685        .downcast::<Editor>()
14686        .unwrap();
14687    let fake_server = fake_servers.next().await.unwrap();
14688
14689    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14690    let multiline_label_2 = "a\nb\nc\n";
14691    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14692    let multiline_description = "d\ne\nf\n";
14693    let multiline_detail_2 = "g\nh\ni\n";
14694
14695    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14696        move |params, _| async move {
14697            Ok(Some(lsp::CompletionResponse::Array(vec![
14698                lsp::CompletionItem {
14699                    label: multiline_label.to_string(),
14700                    text_edit: gen_text_edit(&params, "new_text_1"),
14701                    ..lsp::CompletionItem::default()
14702                },
14703                lsp::CompletionItem {
14704                    label: "single line label 1".to_string(),
14705                    detail: Some(multiline_detail.to_string()),
14706                    text_edit: gen_text_edit(&params, "new_text_2"),
14707                    ..lsp::CompletionItem::default()
14708                },
14709                lsp::CompletionItem {
14710                    label: "single line label 2".to_string(),
14711                    label_details: Some(lsp::CompletionItemLabelDetails {
14712                        description: Some(multiline_description.to_string()),
14713                        detail: None,
14714                    }),
14715                    text_edit: gen_text_edit(&params, "new_text_2"),
14716                    ..lsp::CompletionItem::default()
14717                },
14718                lsp::CompletionItem {
14719                    label: multiline_label_2.to_string(),
14720                    detail: Some(multiline_detail_2.to_string()),
14721                    text_edit: gen_text_edit(&params, "new_text_3"),
14722                    ..lsp::CompletionItem::default()
14723                },
14724                lsp::CompletionItem {
14725                    label: "Label with many     spaces and \t but without newlines".to_string(),
14726                    detail: Some(
14727                        "Details with many     spaces and \t but without newlines".to_string(),
14728                    ),
14729                    text_edit: gen_text_edit(&params, "new_text_4"),
14730                    ..lsp::CompletionItem::default()
14731                },
14732            ])))
14733        },
14734    );
14735
14736    editor.update_in(cx, |editor, window, cx| {
14737        cx.focus_self(window);
14738        editor.move_to_end(&MoveToEnd, window, cx);
14739        editor.handle_input(".", window, cx);
14740    });
14741    cx.run_until_parked();
14742    completion_handle.next().await.unwrap();
14743
14744    editor.update(cx, |editor, _| {
14745        assert!(editor.context_menu_visible());
14746        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14747        {
14748            let completion_labels = menu
14749                .completions
14750                .borrow()
14751                .iter()
14752                .map(|c| c.label.text.clone())
14753                .collect::<Vec<_>>();
14754            assert_eq!(
14755                completion_labels,
14756                &[
14757                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14758                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14759                    "single line label 2 d e f ",
14760                    "a b c g h i ",
14761                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14762                ],
14763                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14764            );
14765
14766            for completion in menu
14767                .completions
14768                .borrow()
14769                .iter() {
14770                    assert_eq!(
14771                        completion.label.filter_range,
14772                        0..completion.label.text.len(),
14773                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14774                    );
14775                }
14776        } else {
14777            panic!("expected completion menu to be open");
14778        }
14779    });
14780}
14781
14782#[gpui::test]
14783async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14784    init_test(cx, |_| {});
14785    let mut cx = EditorLspTestContext::new_rust(
14786        lsp::ServerCapabilities {
14787            completion_provider: Some(lsp::CompletionOptions {
14788                trigger_characters: Some(vec![".".to_string()]),
14789                ..Default::default()
14790            }),
14791            ..Default::default()
14792        },
14793        cx,
14794    )
14795    .await;
14796    cx.lsp
14797        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14798            Ok(Some(lsp::CompletionResponse::Array(vec![
14799                lsp::CompletionItem {
14800                    label: "first".into(),
14801                    ..Default::default()
14802                },
14803                lsp::CompletionItem {
14804                    label: "last".into(),
14805                    ..Default::default()
14806                },
14807            ])))
14808        });
14809    cx.set_state("variableˇ");
14810    cx.simulate_keystroke(".");
14811    cx.executor().run_until_parked();
14812
14813    cx.update_editor(|editor, _, _| {
14814        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14815        {
14816            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14817        } else {
14818            panic!("expected completion menu to be open");
14819        }
14820    });
14821
14822    cx.update_editor(|editor, window, cx| {
14823        editor.move_page_down(&MovePageDown::default(), window, cx);
14824        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14825        {
14826            assert!(
14827                menu.selected_item == 1,
14828                "expected PageDown to select the last item from the context menu"
14829            );
14830        } else {
14831            panic!("expected completion menu to stay open after PageDown");
14832        }
14833    });
14834
14835    cx.update_editor(|editor, window, cx| {
14836        editor.move_page_up(&MovePageUp::default(), window, cx);
14837        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14838        {
14839            assert!(
14840                menu.selected_item == 0,
14841                "expected PageUp to select the first item from the context menu"
14842            );
14843        } else {
14844            panic!("expected completion menu to stay open after PageUp");
14845        }
14846    });
14847}
14848
14849#[gpui::test]
14850async fn test_as_is_completions(cx: &mut TestAppContext) {
14851    init_test(cx, |_| {});
14852    let mut cx = EditorLspTestContext::new_rust(
14853        lsp::ServerCapabilities {
14854            completion_provider: Some(lsp::CompletionOptions {
14855                ..Default::default()
14856            }),
14857            ..Default::default()
14858        },
14859        cx,
14860    )
14861    .await;
14862    cx.lsp
14863        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14864            Ok(Some(lsp::CompletionResponse::Array(vec![
14865                lsp::CompletionItem {
14866                    label: "unsafe".into(),
14867                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14868                        range: lsp::Range {
14869                            start: lsp::Position {
14870                                line: 1,
14871                                character: 2,
14872                            },
14873                            end: lsp::Position {
14874                                line: 1,
14875                                character: 3,
14876                            },
14877                        },
14878                        new_text: "unsafe".to_string(),
14879                    })),
14880                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
14881                    ..Default::default()
14882                },
14883            ])))
14884        });
14885    cx.set_state("fn a() {}\n");
14886    cx.executor().run_until_parked();
14887    cx.update_editor(|editor, window, cx| {
14888        editor.show_completions(
14889            &ShowCompletions {
14890                trigger: Some("\n".into()),
14891            },
14892            window,
14893            cx,
14894        );
14895    });
14896    cx.executor().run_until_parked();
14897
14898    cx.update_editor(|editor, window, cx| {
14899        editor.confirm_completion(&Default::default(), window, cx)
14900    });
14901    cx.executor().run_until_parked();
14902    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
14903}
14904
14905#[gpui::test]
14906async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
14907    init_test(cx, |_| {});
14908    let language =
14909        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
14910    let mut cx = EditorLspTestContext::new(
14911        language,
14912        lsp::ServerCapabilities {
14913            completion_provider: Some(lsp::CompletionOptions {
14914                ..lsp::CompletionOptions::default()
14915            }),
14916            ..lsp::ServerCapabilities::default()
14917        },
14918        cx,
14919    )
14920    .await;
14921
14922    cx.set_state(
14923        "#ifndef BAR_H
14924#define BAR_H
14925
14926#include <stdbool.h>
14927
14928int fn_branch(bool do_branch1, bool do_branch2);
14929
14930#endif // BAR_H
14931ˇ",
14932    );
14933    cx.executor().run_until_parked();
14934    cx.update_editor(|editor, window, cx| {
14935        editor.handle_input("#", window, cx);
14936    });
14937    cx.executor().run_until_parked();
14938    cx.update_editor(|editor, window, cx| {
14939        editor.handle_input("i", window, cx);
14940    });
14941    cx.executor().run_until_parked();
14942    cx.update_editor(|editor, window, cx| {
14943        editor.handle_input("n", window, cx);
14944    });
14945    cx.executor().run_until_parked();
14946    cx.assert_editor_state(
14947        "#ifndef BAR_H
14948#define BAR_H
14949
14950#include <stdbool.h>
14951
14952int fn_branch(bool do_branch1, bool do_branch2);
14953
14954#endif // BAR_H
14955#inˇ",
14956    );
14957
14958    cx.lsp
14959        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14960            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14961                is_incomplete: false,
14962                item_defaults: None,
14963                items: vec![lsp::CompletionItem {
14964                    kind: Some(lsp::CompletionItemKind::SNIPPET),
14965                    label_details: Some(lsp::CompletionItemLabelDetails {
14966                        detail: Some("header".to_string()),
14967                        description: None,
14968                    }),
14969                    label: " include".to_string(),
14970                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14971                        range: lsp::Range {
14972                            start: lsp::Position {
14973                                line: 8,
14974                                character: 1,
14975                            },
14976                            end: lsp::Position {
14977                                line: 8,
14978                                character: 1,
14979                            },
14980                        },
14981                        new_text: "include \"$0\"".to_string(),
14982                    })),
14983                    sort_text: Some("40b67681include".to_string()),
14984                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14985                    filter_text: Some("include".to_string()),
14986                    insert_text: Some("include \"$0\"".to_string()),
14987                    ..lsp::CompletionItem::default()
14988                }],
14989            })))
14990        });
14991    cx.update_editor(|editor, window, cx| {
14992        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14993    });
14994    cx.executor().run_until_parked();
14995    cx.update_editor(|editor, window, cx| {
14996        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
14997    });
14998    cx.executor().run_until_parked();
14999    cx.assert_editor_state(
15000        "#ifndef BAR_H
15001#define BAR_H
15002
15003#include <stdbool.h>
15004
15005int fn_branch(bool do_branch1, bool do_branch2);
15006
15007#endif // BAR_H
15008#include \"ˇ\"",
15009    );
15010
15011    cx.lsp
15012        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15013            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15014                is_incomplete: true,
15015                item_defaults: None,
15016                items: vec![lsp::CompletionItem {
15017                    kind: Some(lsp::CompletionItemKind::FILE),
15018                    label: "AGL/".to_string(),
15019                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15020                        range: lsp::Range {
15021                            start: lsp::Position {
15022                                line: 8,
15023                                character: 10,
15024                            },
15025                            end: lsp::Position {
15026                                line: 8,
15027                                character: 11,
15028                            },
15029                        },
15030                        new_text: "AGL/".to_string(),
15031                    })),
15032                    sort_text: Some("40b67681AGL/".to_string()),
15033                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15034                    filter_text: Some("AGL/".to_string()),
15035                    insert_text: Some("AGL/".to_string()),
15036                    ..lsp::CompletionItem::default()
15037                }],
15038            })))
15039        });
15040    cx.update_editor(|editor, window, cx| {
15041        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15042    });
15043    cx.executor().run_until_parked();
15044    cx.update_editor(|editor, window, cx| {
15045        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15046    });
15047    cx.executor().run_until_parked();
15048    cx.assert_editor_state(
15049        r##"#ifndef BAR_H
15050#define BAR_H
15051
15052#include <stdbool.h>
15053
15054int fn_branch(bool do_branch1, bool do_branch2);
15055
15056#endif // BAR_H
15057#include "AGL/ˇ"##,
15058    );
15059
15060    cx.update_editor(|editor, window, cx| {
15061        editor.handle_input("\"", window, cx);
15062    });
15063    cx.executor().run_until_parked();
15064    cx.assert_editor_state(
15065        r##"#ifndef BAR_H
15066#define BAR_H
15067
15068#include <stdbool.h>
15069
15070int fn_branch(bool do_branch1, bool do_branch2);
15071
15072#endif // BAR_H
15073#include "AGL/"ˇ"##,
15074    );
15075}
15076
15077#[gpui::test]
15078async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15079    init_test(cx, |_| {});
15080
15081    let mut cx = EditorLspTestContext::new_rust(
15082        lsp::ServerCapabilities {
15083            completion_provider: Some(lsp::CompletionOptions {
15084                trigger_characters: Some(vec![".".to_string()]),
15085                resolve_provider: Some(true),
15086                ..Default::default()
15087            }),
15088            ..Default::default()
15089        },
15090        cx,
15091    )
15092    .await;
15093
15094    cx.set_state("fn main() { let a = 2ˇ; }");
15095    cx.simulate_keystroke(".");
15096    let completion_item = lsp::CompletionItem {
15097        label: "Some".into(),
15098        kind: Some(lsp::CompletionItemKind::SNIPPET),
15099        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15100        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15101            kind: lsp::MarkupKind::Markdown,
15102            value: "```rust\nSome(2)\n```".to_string(),
15103        })),
15104        deprecated: Some(false),
15105        sort_text: Some("Some".to_string()),
15106        filter_text: Some("Some".to_string()),
15107        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15108        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15109            range: lsp::Range {
15110                start: lsp::Position {
15111                    line: 0,
15112                    character: 22,
15113                },
15114                end: lsp::Position {
15115                    line: 0,
15116                    character: 22,
15117                },
15118            },
15119            new_text: "Some(2)".to_string(),
15120        })),
15121        additional_text_edits: Some(vec![lsp::TextEdit {
15122            range: lsp::Range {
15123                start: lsp::Position {
15124                    line: 0,
15125                    character: 20,
15126                },
15127                end: lsp::Position {
15128                    line: 0,
15129                    character: 22,
15130                },
15131            },
15132            new_text: "".to_string(),
15133        }]),
15134        ..Default::default()
15135    };
15136
15137    let closure_completion_item = completion_item.clone();
15138    let counter = Arc::new(AtomicUsize::new(0));
15139    let counter_clone = counter.clone();
15140    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15141        let task_completion_item = closure_completion_item.clone();
15142        counter_clone.fetch_add(1, atomic::Ordering::Release);
15143        async move {
15144            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15145                is_incomplete: true,
15146                item_defaults: None,
15147                items: vec![task_completion_item],
15148            })))
15149        }
15150    });
15151
15152    cx.condition(|editor, _| editor.context_menu_visible())
15153        .await;
15154    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15155    assert!(request.next().await.is_some());
15156    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15157
15158    cx.simulate_keystrokes("S o m");
15159    cx.condition(|editor, _| editor.context_menu_visible())
15160        .await;
15161    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15162    assert!(request.next().await.is_some());
15163    assert!(request.next().await.is_some());
15164    assert!(request.next().await.is_some());
15165    request.close();
15166    assert!(request.next().await.is_none());
15167    assert_eq!(
15168        counter.load(atomic::Ordering::Acquire),
15169        4,
15170        "With the completions menu open, only one LSP request should happen per input"
15171    );
15172}
15173
15174#[gpui::test]
15175async fn test_toggle_comment(cx: &mut TestAppContext) {
15176    init_test(cx, |_| {});
15177    let mut cx = EditorTestContext::new(cx).await;
15178    let language = Arc::new(Language::new(
15179        LanguageConfig {
15180            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15181            ..Default::default()
15182        },
15183        Some(tree_sitter_rust::LANGUAGE.into()),
15184    ));
15185    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15186
15187    // If multiple selections intersect a line, the line is only toggled once.
15188    cx.set_state(indoc! {"
15189        fn a() {
15190            «//b();
15191            ˇ»// «c();
15192            //ˇ»  d();
15193        }
15194    "});
15195
15196    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15197
15198    cx.assert_editor_state(indoc! {"
15199        fn a() {
15200            «b();
15201            c();
15202            ˇ» d();
15203        }
15204    "});
15205
15206    // The comment prefix is inserted at the same column for every line in a
15207    // selection.
15208    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15209
15210    cx.assert_editor_state(indoc! {"
15211        fn a() {
15212            // «b();
15213            // c();
15214            ˇ»//  d();
15215        }
15216    "});
15217
15218    // If a selection ends at the beginning of a line, that line is not toggled.
15219    cx.set_selections_state(indoc! {"
15220        fn a() {
15221            // b();
15222            «// c();
15223        ˇ»    //  d();
15224        }
15225    "});
15226
15227    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15228
15229    cx.assert_editor_state(indoc! {"
15230        fn a() {
15231            // b();
15232            «c();
15233        ˇ»    //  d();
15234        }
15235    "});
15236
15237    // If a selection span a single line and is empty, the line is toggled.
15238    cx.set_state(indoc! {"
15239        fn a() {
15240            a();
15241            b();
15242        ˇ
15243        }
15244    "});
15245
15246    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15247
15248    cx.assert_editor_state(indoc! {"
15249        fn a() {
15250            a();
15251            b();
15252        //•ˇ
15253        }
15254    "});
15255
15256    // If a selection span multiple lines, empty lines are not toggled.
15257    cx.set_state(indoc! {"
15258        fn a() {
15259            «a();
15260
15261            c();ˇ»
15262        }
15263    "});
15264
15265    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15266
15267    cx.assert_editor_state(indoc! {"
15268        fn a() {
15269            // «a();
15270
15271            // c();ˇ»
15272        }
15273    "});
15274
15275    // If a selection includes multiple comment prefixes, all lines are uncommented.
15276    cx.set_state(indoc! {"
15277        fn a() {
15278            «// a();
15279            /// b();
15280            //! c();ˇ»
15281        }
15282    "});
15283
15284    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15285
15286    cx.assert_editor_state(indoc! {"
15287        fn a() {
15288            «a();
15289            b();
15290            c();ˇ»
15291        }
15292    "});
15293}
15294
15295#[gpui::test]
15296async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15297    init_test(cx, |_| {});
15298    let mut cx = EditorTestContext::new(cx).await;
15299    let language = Arc::new(Language::new(
15300        LanguageConfig {
15301            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15302            ..Default::default()
15303        },
15304        Some(tree_sitter_rust::LANGUAGE.into()),
15305    ));
15306    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15307
15308    let toggle_comments = &ToggleComments {
15309        advance_downwards: false,
15310        ignore_indent: true,
15311    };
15312
15313    // If multiple selections intersect a line, the line is only toggled once.
15314    cx.set_state(indoc! {"
15315        fn a() {
15316        //    «b();
15317        //    c();
15318        //    ˇ» d();
15319        }
15320    "});
15321
15322    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15323
15324    cx.assert_editor_state(indoc! {"
15325        fn a() {
15326            «b();
15327            c();
15328            ˇ» d();
15329        }
15330    "});
15331
15332    // The comment prefix is inserted at the beginning of each line
15333    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15334
15335    cx.assert_editor_state(indoc! {"
15336        fn a() {
15337        //    «b();
15338        //    c();
15339        //    ˇ» d();
15340        }
15341    "});
15342
15343    // If a selection ends at the beginning of a line, that line is not toggled.
15344    cx.set_selections_state(indoc! {"
15345        fn a() {
15346        //    b();
15347        //    «c();
15348        ˇ»//     d();
15349        }
15350    "});
15351
15352    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15353
15354    cx.assert_editor_state(indoc! {"
15355        fn a() {
15356        //    b();
15357            «c();
15358        ˇ»//     d();
15359        }
15360    "});
15361
15362    // If a selection span a single line and is empty, the line is toggled.
15363    cx.set_state(indoc! {"
15364        fn a() {
15365            a();
15366            b();
15367        ˇ
15368        }
15369    "});
15370
15371    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15372
15373    cx.assert_editor_state(indoc! {"
15374        fn a() {
15375            a();
15376            b();
15377        //ˇ
15378        }
15379    "});
15380
15381    // If a selection span multiple lines, empty lines are not toggled.
15382    cx.set_state(indoc! {"
15383        fn a() {
15384            «a();
15385
15386            c();ˇ»
15387        }
15388    "});
15389
15390    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15391
15392    cx.assert_editor_state(indoc! {"
15393        fn a() {
15394        //    «a();
15395
15396        //    c();ˇ»
15397        }
15398    "});
15399
15400    // If a selection includes multiple comment prefixes, all lines are uncommented.
15401    cx.set_state(indoc! {"
15402        fn a() {
15403        //    «a();
15404        ///    b();
15405        //!    c();ˇ»
15406        }
15407    "});
15408
15409    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15410
15411    cx.assert_editor_state(indoc! {"
15412        fn a() {
15413            «a();
15414            b();
15415            c();ˇ»
15416        }
15417    "});
15418}
15419
15420#[gpui::test]
15421async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15422    init_test(cx, |_| {});
15423
15424    let language = Arc::new(Language::new(
15425        LanguageConfig {
15426            line_comments: vec!["// ".into()],
15427            ..Default::default()
15428        },
15429        Some(tree_sitter_rust::LANGUAGE.into()),
15430    ));
15431
15432    let mut cx = EditorTestContext::new(cx).await;
15433
15434    cx.language_registry().add(language.clone());
15435    cx.update_buffer(|buffer, cx| {
15436        buffer.set_language(Some(language), cx);
15437    });
15438
15439    let toggle_comments = &ToggleComments {
15440        advance_downwards: true,
15441        ignore_indent: false,
15442    };
15443
15444    // Single cursor on one line -> advance
15445    // Cursor moves horizontally 3 characters as well on non-blank line
15446    cx.set_state(indoc!(
15447        "fn a() {
15448             ˇdog();
15449             cat();
15450        }"
15451    ));
15452    cx.update_editor(|editor, window, cx| {
15453        editor.toggle_comments(toggle_comments, window, cx);
15454    });
15455    cx.assert_editor_state(indoc!(
15456        "fn a() {
15457             // dog();
15458             catˇ();
15459        }"
15460    ));
15461
15462    // Single selection on one line -> don't advance
15463    cx.set_state(indoc!(
15464        "fn a() {
15465             «dog()ˇ»;
15466             cat();
15467        }"
15468    ));
15469    cx.update_editor(|editor, window, cx| {
15470        editor.toggle_comments(toggle_comments, window, cx);
15471    });
15472    cx.assert_editor_state(indoc!(
15473        "fn a() {
15474             // «dog()ˇ»;
15475             cat();
15476        }"
15477    ));
15478
15479    // Multiple cursors on one line -> advance
15480    cx.set_state(indoc!(
15481        "fn a() {
15482             ˇdˇog();
15483             cat();
15484        }"
15485    ));
15486    cx.update_editor(|editor, window, cx| {
15487        editor.toggle_comments(toggle_comments, window, cx);
15488    });
15489    cx.assert_editor_state(indoc!(
15490        "fn a() {
15491             // dog();
15492             catˇ(ˇ);
15493        }"
15494    ));
15495
15496    // Multiple cursors on one line, with selection -> don't advance
15497    cx.set_state(indoc!(
15498        "fn a() {
15499             ˇdˇog«()ˇ»;
15500             cat();
15501        }"
15502    ));
15503    cx.update_editor(|editor, window, cx| {
15504        editor.toggle_comments(toggle_comments, window, cx);
15505    });
15506    cx.assert_editor_state(indoc!(
15507        "fn a() {
15508             // ˇdˇog«()ˇ»;
15509             cat();
15510        }"
15511    ));
15512
15513    // Single cursor on one line -> advance
15514    // Cursor moves to column 0 on blank line
15515    cx.set_state(indoc!(
15516        "fn a() {
15517             ˇdog();
15518
15519             cat();
15520        }"
15521    ));
15522    cx.update_editor(|editor, window, cx| {
15523        editor.toggle_comments(toggle_comments, window, cx);
15524    });
15525    cx.assert_editor_state(indoc!(
15526        "fn a() {
15527             // dog();
15528        ˇ
15529             cat();
15530        }"
15531    ));
15532
15533    // Single cursor on one line -> advance
15534    // Cursor starts and ends at column 0
15535    cx.set_state(indoc!(
15536        "fn a() {
15537         ˇ    dog();
15538             cat();
15539        }"
15540    ));
15541    cx.update_editor(|editor, window, cx| {
15542        editor.toggle_comments(toggle_comments, window, cx);
15543    });
15544    cx.assert_editor_state(indoc!(
15545        "fn a() {
15546             // dog();
15547         ˇ    cat();
15548        }"
15549    ));
15550}
15551
15552#[gpui::test]
15553async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15554    init_test(cx, |_| {});
15555
15556    let mut cx = EditorTestContext::new(cx).await;
15557
15558    let html_language = Arc::new(
15559        Language::new(
15560            LanguageConfig {
15561                name: "HTML".into(),
15562                block_comment: Some(BlockCommentConfig {
15563                    start: "<!-- ".into(),
15564                    prefix: "".into(),
15565                    end: " -->".into(),
15566                    tab_size: 0,
15567                }),
15568                ..Default::default()
15569            },
15570            Some(tree_sitter_html::LANGUAGE.into()),
15571        )
15572        .with_injection_query(
15573            r#"
15574            (script_element
15575                (raw_text) @injection.content
15576                (#set! injection.language "javascript"))
15577            "#,
15578        )
15579        .unwrap(),
15580    );
15581
15582    let javascript_language = Arc::new(Language::new(
15583        LanguageConfig {
15584            name: "JavaScript".into(),
15585            line_comments: vec!["// ".into()],
15586            ..Default::default()
15587        },
15588        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15589    ));
15590
15591    cx.language_registry().add(html_language.clone());
15592    cx.language_registry().add(javascript_language);
15593    cx.update_buffer(|buffer, cx| {
15594        buffer.set_language(Some(html_language), cx);
15595    });
15596
15597    // Toggle comments for empty selections
15598    cx.set_state(
15599        &r#"
15600            <p>A</p>ˇ
15601            <p>B</p>ˇ
15602            <p>C</p>ˇ
15603        "#
15604        .unindent(),
15605    );
15606    cx.update_editor(|editor, window, cx| {
15607        editor.toggle_comments(&ToggleComments::default(), window, cx)
15608    });
15609    cx.assert_editor_state(
15610        &r#"
15611            <!-- <p>A</p>ˇ -->
15612            <!-- <p>B</p>ˇ -->
15613            <!-- <p>C</p>ˇ -->
15614        "#
15615        .unindent(),
15616    );
15617    cx.update_editor(|editor, window, cx| {
15618        editor.toggle_comments(&ToggleComments::default(), window, cx)
15619    });
15620    cx.assert_editor_state(
15621        &r#"
15622            <p>A</p>ˇ
15623            <p>B</p>ˇ
15624            <p>C</p>ˇ
15625        "#
15626        .unindent(),
15627    );
15628
15629    // Toggle comments for mixture of empty and non-empty selections, where
15630    // multiple selections occupy a given line.
15631    cx.set_state(
15632        &r#"
15633            <p>A«</p>
15634            <p>ˇ»B</p>ˇ
15635            <p>C«</p>
15636            <p>ˇ»D</p>ˇ
15637        "#
15638        .unindent(),
15639    );
15640
15641    cx.update_editor(|editor, window, cx| {
15642        editor.toggle_comments(&ToggleComments::default(), window, cx)
15643    });
15644    cx.assert_editor_state(
15645        &r#"
15646            <!-- <p>A«</p>
15647            <p>ˇ»B</p>ˇ -->
15648            <!-- <p>C«</p>
15649            <p>ˇ»D</p>ˇ -->
15650        "#
15651        .unindent(),
15652    );
15653    cx.update_editor(|editor, window, cx| {
15654        editor.toggle_comments(&ToggleComments::default(), window, cx)
15655    });
15656    cx.assert_editor_state(
15657        &r#"
15658            <p>A«</p>
15659            <p>ˇ»B</p>ˇ
15660            <p>C«</p>
15661            <p>ˇ»D</p>ˇ
15662        "#
15663        .unindent(),
15664    );
15665
15666    // Toggle comments when different languages are active for different
15667    // selections.
15668    cx.set_state(
15669        &r#"
15670            ˇ<script>
15671                ˇvar x = new Y();
15672            ˇ</script>
15673        "#
15674        .unindent(),
15675    );
15676    cx.executor().run_until_parked();
15677    cx.update_editor(|editor, window, cx| {
15678        editor.toggle_comments(&ToggleComments::default(), window, cx)
15679    });
15680    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15681    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15682    cx.assert_editor_state(
15683        &r#"
15684            <!-- ˇ<script> -->
15685                // ˇvar x = new Y();
15686            <!-- ˇ</script> -->
15687        "#
15688        .unindent(),
15689    );
15690}
15691
15692#[gpui::test]
15693fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15694    init_test(cx, |_| {});
15695
15696    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15697    let multibuffer = cx.new(|cx| {
15698        let mut multibuffer = MultiBuffer::new(ReadWrite);
15699        multibuffer.push_excerpts(
15700            buffer.clone(),
15701            [
15702                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15703                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15704            ],
15705            cx,
15706        );
15707        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15708        multibuffer
15709    });
15710
15711    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15712    editor.update_in(cx, |editor, window, cx| {
15713        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15714        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15715            s.select_ranges([
15716                Point::new(0, 0)..Point::new(0, 0),
15717                Point::new(1, 0)..Point::new(1, 0),
15718            ])
15719        });
15720
15721        editor.handle_input("X", window, cx);
15722        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15723        assert_eq!(
15724            editor.selections.ranges(cx),
15725            [
15726                Point::new(0, 1)..Point::new(0, 1),
15727                Point::new(1, 1)..Point::new(1, 1),
15728            ]
15729        );
15730
15731        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15732        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15733            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15734        });
15735        editor.backspace(&Default::default(), window, cx);
15736        assert_eq!(editor.text(cx), "Xa\nbbb");
15737        assert_eq!(
15738            editor.selections.ranges(cx),
15739            [Point::new(1, 0)..Point::new(1, 0)]
15740        );
15741
15742        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15743            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15744        });
15745        editor.backspace(&Default::default(), window, cx);
15746        assert_eq!(editor.text(cx), "X\nbb");
15747        assert_eq!(
15748            editor.selections.ranges(cx),
15749            [Point::new(0, 1)..Point::new(0, 1)]
15750        );
15751    });
15752}
15753
15754#[gpui::test]
15755fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15756    init_test(cx, |_| {});
15757
15758    let markers = vec![('[', ']').into(), ('(', ')').into()];
15759    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15760        indoc! {"
15761            [aaaa
15762            (bbbb]
15763            cccc)",
15764        },
15765        markers.clone(),
15766    );
15767    let excerpt_ranges = markers.into_iter().map(|marker| {
15768        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15769        ExcerptRange::new(context)
15770    });
15771    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15772    let multibuffer = cx.new(|cx| {
15773        let mut multibuffer = MultiBuffer::new(ReadWrite);
15774        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15775        multibuffer
15776    });
15777
15778    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15779    editor.update_in(cx, |editor, window, cx| {
15780        let (expected_text, selection_ranges) = marked_text_ranges(
15781            indoc! {"
15782                aaaa
15783                bˇbbb
15784                bˇbbˇb
15785                cccc"
15786            },
15787            true,
15788        );
15789        assert_eq!(editor.text(cx), expected_text);
15790        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15791            s.select_ranges(selection_ranges)
15792        });
15793
15794        editor.handle_input("X", window, cx);
15795
15796        let (expected_text, expected_selections) = marked_text_ranges(
15797            indoc! {"
15798                aaaa
15799                bXˇbbXb
15800                bXˇbbXˇb
15801                cccc"
15802            },
15803            false,
15804        );
15805        assert_eq!(editor.text(cx), expected_text);
15806        assert_eq!(editor.selections.ranges(cx), expected_selections);
15807
15808        editor.newline(&Newline, window, cx);
15809        let (expected_text, expected_selections) = marked_text_ranges(
15810            indoc! {"
15811                aaaa
15812                bX
15813                ˇbbX
15814                b
15815                bX
15816                ˇbbX
15817                ˇb
15818                cccc"
15819            },
15820            false,
15821        );
15822        assert_eq!(editor.text(cx), expected_text);
15823        assert_eq!(editor.selections.ranges(cx), expected_selections);
15824    });
15825}
15826
15827#[gpui::test]
15828fn test_refresh_selections(cx: &mut TestAppContext) {
15829    init_test(cx, |_| {});
15830
15831    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15832    let mut excerpt1_id = None;
15833    let multibuffer = cx.new(|cx| {
15834        let mut multibuffer = MultiBuffer::new(ReadWrite);
15835        excerpt1_id = multibuffer
15836            .push_excerpts(
15837                buffer.clone(),
15838                [
15839                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15840                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15841                ],
15842                cx,
15843            )
15844            .into_iter()
15845            .next();
15846        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15847        multibuffer
15848    });
15849
15850    let editor = cx.add_window(|window, cx| {
15851        let mut editor = build_editor(multibuffer.clone(), window, cx);
15852        let snapshot = editor.snapshot(window, cx);
15853        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15854            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15855        });
15856        editor.begin_selection(
15857            Point::new(2, 1).to_display_point(&snapshot),
15858            true,
15859            1,
15860            window,
15861            cx,
15862        );
15863        assert_eq!(
15864            editor.selections.ranges(cx),
15865            [
15866                Point::new(1, 3)..Point::new(1, 3),
15867                Point::new(2, 1)..Point::new(2, 1),
15868            ]
15869        );
15870        editor
15871    });
15872
15873    // Refreshing selections is a no-op when excerpts haven't changed.
15874    _ = editor.update(cx, |editor, window, cx| {
15875        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15876        assert_eq!(
15877            editor.selections.ranges(cx),
15878            [
15879                Point::new(1, 3)..Point::new(1, 3),
15880                Point::new(2, 1)..Point::new(2, 1),
15881            ]
15882        );
15883    });
15884
15885    multibuffer.update(cx, |multibuffer, cx| {
15886        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15887    });
15888    _ = editor.update(cx, |editor, window, cx| {
15889        // Removing an excerpt causes the first selection to become degenerate.
15890        assert_eq!(
15891            editor.selections.ranges(cx),
15892            [
15893                Point::new(0, 0)..Point::new(0, 0),
15894                Point::new(0, 1)..Point::new(0, 1)
15895            ]
15896        );
15897
15898        // Refreshing selections will relocate the first selection to the original buffer
15899        // location.
15900        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15901        assert_eq!(
15902            editor.selections.ranges(cx),
15903            [
15904                Point::new(0, 1)..Point::new(0, 1),
15905                Point::new(0, 3)..Point::new(0, 3)
15906            ]
15907        );
15908        assert!(editor.selections.pending_anchor().is_some());
15909    });
15910}
15911
15912#[gpui::test]
15913fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
15914    init_test(cx, |_| {});
15915
15916    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15917    let mut excerpt1_id = None;
15918    let multibuffer = cx.new(|cx| {
15919        let mut multibuffer = MultiBuffer::new(ReadWrite);
15920        excerpt1_id = multibuffer
15921            .push_excerpts(
15922                buffer.clone(),
15923                [
15924                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15925                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15926                ],
15927                cx,
15928            )
15929            .into_iter()
15930            .next();
15931        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15932        multibuffer
15933    });
15934
15935    let editor = cx.add_window(|window, cx| {
15936        let mut editor = build_editor(multibuffer.clone(), window, cx);
15937        let snapshot = editor.snapshot(window, cx);
15938        editor.begin_selection(
15939            Point::new(1, 3).to_display_point(&snapshot),
15940            false,
15941            1,
15942            window,
15943            cx,
15944        );
15945        assert_eq!(
15946            editor.selections.ranges(cx),
15947            [Point::new(1, 3)..Point::new(1, 3)]
15948        );
15949        editor
15950    });
15951
15952    multibuffer.update(cx, |multibuffer, cx| {
15953        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15954    });
15955    _ = editor.update(cx, |editor, window, cx| {
15956        assert_eq!(
15957            editor.selections.ranges(cx),
15958            [Point::new(0, 0)..Point::new(0, 0)]
15959        );
15960
15961        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
15962        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15963        assert_eq!(
15964            editor.selections.ranges(cx),
15965            [Point::new(0, 3)..Point::new(0, 3)]
15966        );
15967        assert!(editor.selections.pending_anchor().is_some());
15968    });
15969}
15970
15971#[gpui::test]
15972async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
15973    init_test(cx, |_| {});
15974
15975    let language = Arc::new(
15976        Language::new(
15977            LanguageConfig {
15978                brackets: BracketPairConfig {
15979                    pairs: vec![
15980                        BracketPair {
15981                            start: "{".to_string(),
15982                            end: "}".to_string(),
15983                            close: true,
15984                            surround: true,
15985                            newline: true,
15986                        },
15987                        BracketPair {
15988                            start: "/* ".to_string(),
15989                            end: " */".to_string(),
15990                            close: true,
15991                            surround: true,
15992                            newline: true,
15993                        },
15994                    ],
15995                    ..Default::default()
15996                },
15997                ..Default::default()
15998            },
15999            Some(tree_sitter_rust::LANGUAGE.into()),
16000        )
16001        .with_indents_query("")
16002        .unwrap(),
16003    );
16004
16005    let text = concat!(
16006        "{   }\n",     //
16007        "  x\n",       //
16008        "  /*   */\n", //
16009        "x\n",         //
16010        "{{} }\n",     //
16011    );
16012
16013    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16014    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16015    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16016    editor
16017        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16018        .await;
16019
16020    editor.update_in(cx, |editor, window, cx| {
16021        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16022            s.select_display_ranges([
16023                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16024                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16025                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16026            ])
16027        });
16028        editor.newline(&Newline, window, cx);
16029
16030        assert_eq!(
16031            editor.buffer().read(cx).read(cx).text(),
16032            concat!(
16033                "{ \n",    // Suppress rustfmt
16034                "\n",      //
16035                "}\n",     //
16036                "  x\n",   //
16037                "  /* \n", //
16038                "  \n",    //
16039                "  */\n",  //
16040                "x\n",     //
16041                "{{} \n",  //
16042                "}\n",     //
16043            )
16044        );
16045    });
16046}
16047
16048#[gpui::test]
16049fn test_highlighted_ranges(cx: &mut TestAppContext) {
16050    init_test(cx, |_| {});
16051
16052    let editor = cx.add_window(|window, cx| {
16053        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16054        build_editor(buffer, window, cx)
16055    });
16056
16057    _ = editor.update(cx, |editor, window, cx| {
16058        struct Type1;
16059        struct Type2;
16060
16061        let buffer = editor.buffer.read(cx).snapshot(cx);
16062
16063        let anchor_range =
16064            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16065
16066        editor.highlight_background::<Type1>(
16067            &[
16068                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16069                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16070                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16071                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16072            ],
16073            |_| Hsla::red(),
16074            cx,
16075        );
16076        editor.highlight_background::<Type2>(
16077            &[
16078                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16079                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16080                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16081                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16082            ],
16083            |_| Hsla::green(),
16084            cx,
16085        );
16086
16087        let snapshot = editor.snapshot(window, cx);
16088        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16089            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16090            &snapshot,
16091            cx.theme(),
16092        );
16093        assert_eq!(
16094            highlighted_ranges,
16095            &[
16096                (
16097                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16098                    Hsla::green(),
16099                ),
16100                (
16101                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16102                    Hsla::red(),
16103                ),
16104                (
16105                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16106                    Hsla::green(),
16107                ),
16108                (
16109                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16110                    Hsla::red(),
16111                ),
16112            ]
16113        );
16114        assert_eq!(
16115            editor.sorted_background_highlights_in_range(
16116                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16117                &snapshot,
16118                cx.theme(),
16119            ),
16120            &[(
16121                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16122                Hsla::red(),
16123            )]
16124        );
16125    });
16126}
16127
16128#[gpui::test]
16129async fn test_following(cx: &mut TestAppContext) {
16130    init_test(cx, |_| {});
16131
16132    let fs = FakeFs::new(cx.executor());
16133    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16134
16135    let buffer = project.update(cx, |project, cx| {
16136        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16137        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16138    });
16139    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16140    let follower = cx.update(|cx| {
16141        cx.open_window(
16142            WindowOptions {
16143                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16144                    gpui::Point::new(px(0.), px(0.)),
16145                    gpui::Point::new(px(10.), px(80.)),
16146                ))),
16147                ..Default::default()
16148            },
16149            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16150        )
16151        .unwrap()
16152    });
16153
16154    let is_still_following = Rc::new(RefCell::new(true));
16155    let follower_edit_event_count = Rc::new(RefCell::new(0));
16156    let pending_update = Rc::new(RefCell::new(None));
16157    let leader_entity = leader.root(cx).unwrap();
16158    let follower_entity = follower.root(cx).unwrap();
16159    _ = follower.update(cx, {
16160        let update = pending_update.clone();
16161        let is_still_following = is_still_following.clone();
16162        let follower_edit_event_count = follower_edit_event_count.clone();
16163        |_, window, cx| {
16164            cx.subscribe_in(
16165                &leader_entity,
16166                window,
16167                move |_, leader, event, window, cx| {
16168                    leader.read(cx).add_event_to_update_proto(
16169                        event,
16170                        &mut update.borrow_mut(),
16171                        window,
16172                        cx,
16173                    );
16174                },
16175            )
16176            .detach();
16177
16178            cx.subscribe_in(
16179                &follower_entity,
16180                window,
16181                move |_, _, event: &EditorEvent, _window, _cx| {
16182                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16183                        *is_still_following.borrow_mut() = false;
16184                    }
16185
16186                    if let EditorEvent::BufferEdited = event {
16187                        *follower_edit_event_count.borrow_mut() += 1;
16188                    }
16189                },
16190            )
16191            .detach();
16192        }
16193    });
16194
16195    // Update the selections only
16196    _ = leader.update(cx, |leader, window, cx| {
16197        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16198            s.select_ranges([1..1])
16199        });
16200    });
16201    follower
16202        .update(cx, |follower, window, cx| {
16203            follower.apply_update_proto(
16204                &project,
16205                pending_update.borrow_mut().take().unwrap(),
16206                window,
16207                cx,
16208            )
16209        })
16210        .unwrap()
16211        .await
16212        .unwrap();
16213    _ = follower.update(cx, |follower, _, cx| {
16214        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
16215    });
16216    assert!(*is_still_following.borrow());
16217    assert_eq!(*follower_edit_event_count.borrow(), 0);
16218
16219    // Update the scroll position only
16220    _ = leader.update(cx, |leader, window, cx| {
16221        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16222    });
16223    follower
16224        .update(cx, |follower, window, cx| {
16225            follower.apply_update_proto(
16226                &project,
16227                pending_update.borrow_mut().take().unwrap(),
16228                window,
16229                cx,
16230            )
16231        })
16232        .unwrap()
16233        .await
16234        .unwrap();
16235    assert_eq!(
16236        follower
16237            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16238            .unwrap(),
16239        gpui::Point::new(1.5, 3.5)
16240    );
16241    assert!(*is_still_following.borrow());
16242    assert_eq!(*follower_edit_event_count.borrow(), 0);
16243
16244    // Update the selections and scroll position. The follower's scroll position is updated
16245    // via autoscroll, not via the leader's exact scroll position.
16246    _ = leader.update(cx, |leader, window, cx| {
16247        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16248            s.select_ranges([0..0])
16249        });
16250        leader.request_autoscroll(Autoscroll::newest(), cx);
16251        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16252    });
16253    follower
16254        .update(cx, |follower, window, cx| {
16255            follower.apply_update_proto(
16256                &project,
16257                pending_update.borrow_mut().take().unwrap(),
16258                window,
16259                cx,
16260            )
16261        })
16262        .unwrap()
16263        .await
16264        .unwrap();
16265    _ = follower.update(cx, |follower, _, cx| {
16266        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16267        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16268    });
16269    assert!(*is_still_following.borrow());
16270
16271    // Creating a pending selection that precedes another selection
16272    _ = leader.update(cx, |leader, window, cx| {
16273        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16274            s.select_ranges([1..1])
16275        });
16276        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16277    });
16278    follower
16279        .update(cx, |follower, window, cx| {
16280            follower.apply_update_proto(
16281                &project,
16282                pending_update.borrow_mut().take().unwrap(),
16283                window,
16284                cx,
16285            )
16286        })
16287        .unwrap()
16288        .await
16289        .unwrap();
16290    _ = follower.update(cx, |follower, _, cx| {
16291        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16292    });
16293    assert!(*is_still_following.borrow());
16294
16295    // Extend the pending selection so that it surrounds another selection
16296    _ = leader.update(cx, |leader, window, cx| {
16297        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16298    });
16299    follower
16300        .update(cx, |follower, window, cx| {
16301            follower.apply_update_proto(
16302                &project,
16303                pending_update.borrow_mut().take().unwrap(),
16304                window,
16305                cx,
16306            )
16307        })
16308        .unwrap()
16309        .await
16310        .unwrap();
16311    _ = follower.update(cx, |follower, _, cx| {
16312        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16313    });
16314
16315    // Scrolling locally breaks the follow
16316    _ = follower.update(cx, |follower, window, cx| {
16317        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16318        follower.set_scroll_anchor(
16319            ScrollAnchor {
16320                anchor: top_anchor,
16321                offset: gpui::Point::new(0.0, 0.5),
16322            },
16323            window,
16324            cx,
16325        );
16326    });
16327    assert!(!(*is_still_following.borrow()));
16328}
16329
16330#[gpui::test]
16331async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16332    init_test(cx, |_| {});
16333
16334    let fs = FakeFs::new(cx.executor());
16335    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16336    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16337    let pane = workspace
16338        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16339        .unwrap();
16340
16341    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16342
16343    let leader = pane.update_in(cx, |_, window, cx| {
16344        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16345        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16346    });
16347
16348    // Start following the editor when it has no excerpts.
16349    let mut state_message =
16350        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16351    let workspace_entity = workspace.root(cx).unwrap();
16352    let follower_1 = cx
16353        .update_window(*workspace.deref(), |_, window, cx| {
16354            Editor::from_state_proto(
16355                workspace_entity,
16356                ViewId {
16357                    creator: CollaboratorId::PeerId(PeerId::default()),
16358                    id: 0,
16359                },
16360                &mut state_message,
16361                window,
16362                cx,
16363            )
16364        })
16365        .unwrap()
16366        .unwrap()
16367        .await
16368        .unwrap();
16369
16370    let update_message = Rc::new(RefCell::new(None));
16371    follower_1.update_in(cx, {
16372        let update = update_message.clone();
16373        |_, window, cx| {
16374            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16375                leader.read(cx).add_event_to_update_proto(
16376                    event,
16377                    &mut update.borrow_mut(),
16378                    window,
16379                    cx,
16380                );
16381            })
16382            .detach();
16383        }
16384    });
16385
16386    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16387        (
16388            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16389            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16390        )
16391    });
16392
16393    // Insert some excerpts.
16394    leader.update(cx, |leader, cx| {
16395        leader.buffer.update(cx, |multibuffer, cx| {
16396            multibuffer.set_excerpts_for_path(
16397                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
16398                buffer_1.clone(),
16399                vec![
16400                    Point::row_range(0..3),
16401                    Point::row_range(1..6),
16402                    Point::row_range(12..15),
16403                ],
16404                0,
16405                cx,
16406            );
16407            multibuffer.set_excerpts_for_path(
16408                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
16409                buffer_2.clone(),
16410                vec![Point::row_range(0..6), Point::row_range(8..12)],
16411                0,
16412                cx,
16413            );
16414        });
16415    });
16416
16417    // Apply the update of adding the excerpts.
16418    follower_1
16419        .update_in(cx, |follower, window, cx| {
16420            follower.apply_update_proto(
16421                &project,
16422                update_message.borrow().clone().unwrap(),
16423                window,
16424                cx,
16425            )
16426        })
16427        .await
16428        .unwrap();
16429    assert_eq!(
16430        follower_1.update(cx, |editor, cx| editor.text(cx)),
16431        leader.update(cx, |editor, cx| editor.text(cx))
16432    );
16433    update_message.borrow_mut().take();
16434
16435    // Start following separately after it already has excerpts.
16436    let mut state_message =
16437        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16438    let workspace_entity = workspace.root(cx).unwrap();
16439    let follower_2 = cx
16440        .update_window(*workspace.deref(), |_, window, cx| {
16441            Editor::from_state_proto(
16442                workspace_entity,
16443                ViewId {
16444                    creator: CollaboratorId::PeerId(PeerId::default()),
16445                    id: 0,
16446                },
16447                &mut state_message,
16448                window,
16449                cx,
16450            )
16451        })
16452        .unwrap()
16453        .unwrap()
16454        .await
16455        .unwrap();
16456    assert_eq!(
16457        follower_2.update(cx, |editor, cx| editor.text(cx)),
16458        leader.update(cx, |editor, cx| editor.text(cx))
16459    );
16460
16461    // Remove some excerpts.
16462    leader.update(cx, |leader, cx| {
16463        leader.buffer.update(cx, |multibuffer, cx| {
16464            let excerpt_ids = multibuffer.excerpt_ids();
16465            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16466            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16467        });
16468    });
16469
16470    // Apply the update of removing the excerpts.
16471    follower_1
16472        .update_in(cx, |follower, window, cx| {
16473            follower.apply_update_proto(
16474                &project,
16475                update_message.borrow().clone().unwrap(),
16476                window,
16477                cx,
16478            )
16479        })
16480        .await
16481        .unwrap();
16482    follower_2
16483        .update_in(cx, |follower, window, cx| {
16484            follower.apply_update_proto(
16485                &project,
16486                update_message.borrow().clone().unwrap(),
16487                window,
16488                cx,
16489            )
16490        })
16491        .await
16492        .unwrap();
16493    update_message.borrow_mut().take();
16494    assert_eq!(
16495        follower_1.update(cx, |editor, cx| editor.text(cx)),
16496        leader.update(cx, |editor, cx| editor.text(cx))
16497    );
16498}
16499
16500#[gpui::test]
16501async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16502    init_test(cx, |_| {});
16503
16504    let mut cx = EditorTestContext::new(cx).await;
16505    let lsp_store =
16506        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16507
16508    cx.set_state(indoc! {"
16509        ˇfn func(abc def: i32) -> u32 {
16510        }
16511    "});
16512
16513    cx.update(|_, cx| {
16514        lsp_store.update(cx, |lsp_store, cx| {
16515            lsp_store
16516                .update_diagnostics(
16517                    LanguageServerId(0),
16518                    lsp::PublishDiagnosticsParams {
16519                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16520                        version: None,
16521                        diagnostics: vec![
16522                            lsp::Diagnostic {
16523                                range: lsp::Range::new(
16524                                    lsp::Position::new(0, 11),
16525                                    lsp::Position::new(0, 12),
16526                                ),
16527                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16528                                ..Default::default()
16529                            },
16530                            lsp::Diagnostic {
16531                                range: lsp::Range::new(
16532                                    lsp::Position::new(0, 12),
16533                                    lsp::Position::new(0, 15),
16534                                ),
16535                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16536                                ..Default::default()
16537                            },
16538                            lsp::Diagnostic {
16539                                range: lsp::Range::new(
16540                                    lsp::Position::new(0, 25),
16541                                    lsp::Position::new(0, 28),
16542                                ),
16543                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16544                                ..Default::default()
16545                            },
16546                        ],
16547                    },
16548                    None,
16549                    DiagnosticSourceKind::Pushed,
16550                    &[],
16551                    cx,
16552                )
16553                .unwrap()
16554        });
16555    });
16556
16557    executor.run_until_parked();
16558
16559    cx.update_editor(|editor, window, cx| {
16560        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16561    });
16562
16563    cx.assert_editor_state(indoc! {"
16564        fn func(abc def: i32) -> ˇu32 {
16565        }
16566    "});
16567
16568    cx.update_editor(|editor, window, cx| {
16569        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16570    });
16571
16572    cx.assert_editor_state(indoc! {"
16573        fn func(abc ˇdef: i32) -> u32 {
16574        }
16575    "});
16576
16577    cx.update_editor(|editor, window, cx| {
16578        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16579    });
16580
16581    cx.assert_editor_state(indoc! {"
16582        fn func(abcˇ def: i32) -> u32 {
16583        }
16584    "});
16585
16586    cx.update_editor(|editor, window, cx| {
16587        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16588    });
16589
16590    cx.assert_editor_state(indoc! {"
16591        fn func(abc def: i32) -> ˇu32 {
16592        }
16593    "});
16594}
16595
16596#[gpui::test]
16597async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16598    init_test(cx, |_| {});
16599
16600    let mut cx = EditorTestContext::new(cx).await;
16601
16602    let diff_base = r#"
16603        use some::mod;
16604
16605        const A: u32 = 42;
16606
16607        fn main() {
16608            println!("hello");
16609
16610            println!("world");
16611        }
16612        "#
16613    .unindent();
16614
16615    // Edits are modified, removed, modified, added
16616    cx.set_state(
16617        &r#"
16618        use some::modified;
16619
16620        ˇ
16621        fn main() {
16622            println!("hello there");
16623
16624            println!("around the");
16625            println!("world");
16626        }
16627        "#
16628        .unindent(),
16629    );
16630
16631    cx.set_head_text(&diff_base);
16632    executor.run_until_parked();
16633
16634    cx.update_editor(|editor, window, cx| {
16635        //Wrap around the bottom of the buffer
16636        for _ in 0..3 {
16637            editor.go_to_next_hunk(&GoToHunk, window, cx);
16638        }
16639    });
16640
16641    cx.assert_editor_state(
16642        &r#"
16643        ˇuse some::modified;
16644
16645
16646        fn main() {
16647            println!("hello there");
16648
16649            println!("around the");
16650            println!("world");
16651        }
16652        "#
16653        .unindent(),
16654    );
16655
16656    cx.update_editor(|editor, window, cx| {
16657        //Wrap around the top of the buffer
16658        for _ in 0..2 {
16659            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16660        }
16661    });
16662
16663    cx.assert_editor_state(
16664        &r#"
16665        use some::modified;
16666
16667
16668        fn main() {
16669        ˇ    println!("hello there");
16670
16671            println!("around the");
16672            println!("world");
16673        }
16674        "#
16675        .unindent(),
16676    );
16677
16678    cx.update_editor(|editor, window, cx| {
16679        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16680    });
16681
16682    cx.assert_editor_state(
16683        &r#"
16684        use some::modified;
16685
16686        ˇ
16687        fn main() {
16688            println!("hello there");
16689
16690            println!("around the");
16691            println!("world");
16692        }
16693        "#
16694        .unindent(),
16695    );
16696
16697    cx.update_editor(|editor, window, cx| {
16698        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16699    });
16700
16701    cx.assert_editor_state(
16702        &r#"
16703        ˇuse some::modified;
16704
16705
16706        fn main() {
16707            println!("hello there");
16708
16709            println!("around the");
16710            println!("world");
16711        }
16712        "#
16713        .unindent(),
16714    );
16715
16716    cx.update_editor(|editor, window, cx| {
16717        for _ in 0..2 {
16718            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16719        }
16720    });
16721
16722    cx.assert_editor_state(
16723        &r#"
16724        use some::modified;
16725
16726
16727        fn main() {
16728        ˇ    println!("hello there");
16729
16730            println!("around the");
16731            println!("world");
16732        }
16733        "#
16734        .unindent(),
16735    );
16736
16737    cx.update_editor(|editor, window, cx| {
16738        editor.fold(&Fold, window, cx);
16739    });
16740
16741    cx.update_editor(|editor, window, cx| {
16742        editor.go_to_next_hunk(&GoToHunk, window, cx);
16743    });
16744
16745    cx.assert_editor_state(
16746        &r#"
16747        ˇuse some::modified;
16748
16749
16750        fn main() {
16751            println!("hello there");
16752
16753            println!("around the");
16754            println!("world");
16755        }
16756        "#
16757        .unindent(),
16758    );
16759}
16760
16761#[test]
16762fn test_split_words() {
16763    fn split(text: &str) -> Vec<&str> {
16764        split_words(text).collect()
16765    }
16766
16767    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16768    assert_eq!(split("hello_world"), &["hello_", "world"]);
16769    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16770    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16771    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16772    assert_eq!(split("helloworld"), &["helloworld"]);
16773
16774    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16775}
16776
16777#[gpui::test]
16778async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16779    init_test(cx, |_| {});
16780
16781    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16782    let mut assert = |before, after| {
16783        let _state_context = cx.set_state(before);
16784        cx.run_until_parked();
16785        cx.update_editor(|editor, window, cx| {
16786            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16787        });
16788        cx.run_until_parked();
16789        cx.assert_editor_state(after);
16790    };
16791
16792    // Outside bracket jumps to outside of matching bracket
16793    assert("console.logˇ(var);", "console.log(var)ˇ;");
16794    assert("console.log(var)ˇ;", "console.logˇ(var);");
16795
16796    // Inside bracket jumps to inside of matching bracket
16797    assert("console.log(ˇvar);", "console.log(varˇ);");
16798    assert("console.log(varˇ);", "console.log(ˇvar);");
16799
16800    // When outside a bracket and inside, favor jumping to the inside bracket
16801    assert(
16802        "console.log('foo', [1, 2, 3]ˇ);",
16803        "console.log(ˇ'foo', [1, 2, 3]);",
16804    );
16805    assert(
16806        "console.log(ˇ'foo', [1, 2, 3]);",
16807        "console.log('foo', [1, 2, 3]ˇ);",
16808    );
16809
16810    // Bias forward if two options are equally likely
16811    assert(
16812        "let result = curried_fun()ˇ();",
16813        "let result = curried_fun()()ˇ;",
16814    );
16815
16816    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16817    assert(
16818        indoc! {"
16819            function test() {
16820                console.log('test')ˇ
16821            }"},
16822        indoc! {"
16823            function test() {
16824                console.logˇ('test')
16825            }"},
16826    );
16827}
16828
16829#[gpui::test]
16830async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16831    init_test(cx, |_| {});
16832
16833    let fs = FakeFs::new(cx.executor());
16834    fs.insert_tree(
16835        path!("/a"),
16836        json!({
16837            "main.rs": "fn main() { let a = 5; }",
16838            "other.rs": "// Test file",
16839        }),
16840    )
16841    .await;
16842    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16843
16844    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16845    language_registry.add(Arc::new(Language::new(
16846        LanguageConfig {
16847            name: "Rust".into(),
16848            matcher: LanguageMatcher {
16849                path_suffixes: vec!["rs".to_string()],
16850                ..Default::default()
16851            },
16852            brackets: BracketPairConfig {
16853                pairs: vec![BracketPair {
16854                    start: "{".to_string(),
16855                    end: "}".to_string(),
16856                    close: true,
16857                    surround: true,
16858                    newline: true,
16859                }],
16860                disabled_scopes_by_bracket_ix: Vec::new(),
16861            },
16862            ..Default::default()
16863        },
16864        Some(tree_sitter_rust::LANGUAGE.into()),
16865    )));
16866    let mut fake_servers = language_registry.register_fake_lsp(
16867        "Rust",
16868        FakeLspAdapter {
16869            capabilities: lsp::ServerCapabilities {
16870                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16871                    first_trigger_character: "{".to_string(),
16872                    more_trigger_character: None,
16873                }),
16874                ..Default::default()
16875            },
16876            ..Default::default()
16877        },
16878    );
16879
16880    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16881
16882    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16883
16884    let worktree_id = workspace
16885        .update(cx, |workspace, _, cx| {
16886            workspace.project().update(cx, |project, cx| {
16887                project.worktrees(cx).next().unwrap().read(cx).id()
16888            })
16889        })
16890        .unwrap();
16891
16892    let buffer = project
16893        .update(cx, |project, cx| {
16894            project.open_local_buffer(path!("/a/main.rs"), cx)
16895        })
16896        .await
16897        .unwrap();
16898    let editor_handle = workspace
16899        .update(cx, |workspace, window, cx| {
16900            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
16901        })
16902        .unwrap()
16903        .await
16904        .unwrap()
16905        .downcast::<Editor>()
16906        .unwrap();
16907
16908    cx.executor().start_waiting();
16909    let fake_server = fake_servers.next().await.unwrap();
16910
16911    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
16912        |params, _| async move {
16913            assert_eq!(
16914                params.text_document_position.text_document.uri,
16915                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
16916            );
16917            assert_eq!(
16918                params.text_document_position.position,
16919                lsp::Position::new(0, 21),
16920            );
16921
16922            Ok(Some(vec![lsp::TextEdit {
16923                new_text: "]".to_string(),
16924                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16925            }]))
16926        },
16927    );
16928
16929    editor_handle.update_in(cx, |editor, window, cx| {
16930        window.focus(&editor.focus_handle(cx));
16931        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16932            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
16933        });
16934        editor.handle_input("{", window, cx);
16935    });
16936
16937    cx.executor().run_until_parked();
16938
16939    buffer.update(cx, |buffer, _| {
16940        assert_eq!(
16941            buffer.text(),
16942            "fn main() { let a = {5}; }",
16943            "No extra braces from on type formatting should appear in the buffer"
16944        )
16945    });
16946}
16947
16948#[gpui::test(iterations = 20, seeds(31))]
16949async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
16950    init_test(cx, |_| {});
16951
16952    let mut cx = EditorLspTestContext::new_rust(
16953        lsp::ServerCapabilities {
16954            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16955                first_trigger_character: ".".to_string(),
16956                more_trigger_character: None,
16957            }),
16958            ..Default::default()
16959        },
16960        cx,
16961    )
16962    .await;
16963
16964    cx.update_buffer(|buffer, _| {
16965        // This causes autoindent to be async.
16966        buffer.set_sync_parse_timeout(Duration::ZERO)
16967    });
16968
16969    cx.set_state("fn c() {\n    d()ˇ\n}\n");
16970    cx.simulate_keystroke("\n");
16971    cx.run_until_parked();
16972
16973    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
16974    let mut request =
16975        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
16976            let buffer_cloned = buffer_cloned.clone();
16977            async move {
16978                buffer_cloned.update(&mut cx, |buffer, _| {
16979                    assert_eq!(
16980                        buffer.text(),
16981                        "fn c() {\n    d()\n        .\n}\n",
16982                        "OnTypeFormatting should triggered after autoindent applied"
16983                    )
16984                })?;
16985
16986                Ok(Some(vec![]))
16987            }
16988        });
16989
16990    cx.simulate_keystroke(".");
16991    cx.run_until_parked();
16992
16993    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
16994    assert!(request.next().await.is_some());
16995    request.close();
16996    assert!(request.next().await.is_none());
16997}
16998
16999#[gpui::test]
17000async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17001    init_test(cx, |_| {});
17002
17003    let fs = FakeFs::new(cx.executor());
17004    fs.insert_tree(
17005        path!("/a"),
17006        json!({
17007            "main.rs": "fn main() { let a = 5; }",
17008            "other.rs": "// Test file",
17009        }),
17010    )
17011    .await;
17012
17013    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17014
17015    let server_restarts = Arc::new(AtomicUsize::new(0));
17016    let closure_restarts = Arc::clone(&server_restarts);
17017    let language_server_name = "test language server";
17018    let language_name: LanguageName = "Rust".into();
17019
17020    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17021    language_registry.add(Arc::new(Language::new(
17022        LanguageConfig {
17023            name: language_name.clone(),
17024            matcher: LanguageMatcher {
17025                path_suffixes: vec!["rs".to_string()],
17026                ..Default::default()
17027            },
17028            ..Default::default()
17029        },
17030        Some(tree_sitter_rust::LANGUAGE.into()),
17031    )));
17032    let mut fake_servers = language_registry.register_fake_lsp(
17033        "Rust",
17034        FakeLspAdapter {
17035            name: language_server_name,
17036            initialization_options: Some(json!({
17037                "testOptionValue": true
17038            })),
17039            initializer: Some(Box::new(move |fake_server| {
17040                let task_restarts = Arc::clone(&closure_restarts);
17041                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17042                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17043                    futures::future::ready(Ok(()))
17044                });
17045            })),
17046            ..Default::default()
17047        },
17048    );
17049
17050    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17051    let _buffer = project
17052        .update(cx, |project, cx| {
17053            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17054        })
17055        .await
17056        .unwrap();
17057    let _fake_server = fake_servers.next().await.unwrap();
17058    update_test_language_settings(cx, |language_settings| {
17059        language_settings.languages.0.insert(
17060            language_name.clone().0,
17061            LanguageSettingsContent {
17062                tab_size: NonZeroU32::new(8),
17063                ..Default::default()
17064            },
17065        );
17066    });
17067    cx.executor().run_until_parked();
17068    assert_eq!(
17069        server_restarts.load(atomic::Ordering::Acquire),
17070        0,
17071        "Should not restart LSP server on an unrelated change"
17072    );
17073
17074    update_test_project_settings(cx, |project_settings| {
17075        project_settings.lsp.insert(
17076            "Some other server name".into(),
17077            LspSettings {
17078                binary: None,
17079                settings: None,
17080                initialization_options: Some(json!({
17081                    "some other init value": false
17082                })),
17083                enable_lsp_tasks: false,
17084                fetch: None,
17085            },
17086        );
17087    });
17088    cx.executor().run_until_parked();
17089    assert_eq!(
17090        server_restarts.load(atomic::Ordering::Acquire),
17091        0,
17092        "Should not restart LSP server on an unrelated LSP settings change"
17093    );
17094
17095    update_test_project_settings(cx, |project_settings| {
17096        project_settings.lsp.insert(
17097            language_server_name.into(),
17098            LspSettings {
17099                binary: None,
17100                settings: None,
17101                initialization_options: Some(json!({
17102                    "anotherInitValue": false
17103                })),
17104                enable_lsp_tasks: false,
17105                fetch: None,
17106            },
17107        );
17108    });
17109    cx.executor().run_until_parked();
17110    assert_eq!(
17111        server_restarts.load(atomic::Ordering::Acquire),
17112        1,
17113        "Should restart LSP server on a related LSP settings change"
17114    );
17115
17116    update_test_project_settings(cx, |project_settings| {
17117        project_settings.lsp.insert(
17118            language_server_name.into(),
17119            LspSettings {
17120                binary: None,
17121                settings: None,
17122                initialization_options: Some(json!({
17123                    "anotherInitValue": false
17124                })),
17125                enable_lsp_tasks: false,
17126                fetch: None,
17127            },
17128        );
17129    });
17130    cx.executor().run_until_parked();
17131    assert_eq!(
17132        server_restarts.load(atomic::Ordering::Acquire),
17133        1,
17134        "Should not restart LSP server on a related LSP settings change that is the same"
17135    );
17136
17137    update_test_project_settings(cx, |project_settings| {
17138        project_settings.lsp.insert(
17139            language_server_name.into(),
17140            LspSettings {
17141                binary: None,
17142                settings: None,
17143                initialization_options: None,
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        2,
17153        "Should restart LSP server on another related LSP settings change"
17154    );
17155}
17156
17157#[gpui::test]
17158async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17159    init_test(cx, |_| {});
17160
17161    let mut cx = EditorLspTestContext::new_rust(
17162        lsp::ServerCapabilities {
17163            completion_provider: Some(lsp::CompletionOptions {
17164                trigger_characters: Some(vec![".".to_string()]),
17165                resolve_provider: Some(true),
17166                ..Default::default()
17167            }),
17168            ..Default::default()
17169        },
17170        cx,
17171    )
17172    .await;
17173
17174    cx.set_state("fn main() { let a = 2ˇ; }");
17175    cx.simulate_keystroke(".");
17176    let completion_item = lsp::CompletionItem {
17177        label: "some".into(),
17178        kind: Some(lsp::CompletionItemKind::SNIPPET),
17179        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17180        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17181            kind: lsp::MarkupKind::Markdown,
17182            value: "```rust\nSome(2)\n```".to_string(),
17183        })),
17184        deprecated: Some(false),
17185        sort_text: Some("fffffff2".to_string()),
17186        filter_text: Some("some".to_string()),
17187        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17188        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17189            range: lsp::Range {
17190                start: lsp::Position {
17191                    line: 0,
17192                    character: 22,
17193                },
17194                end: lsp::Position {
17195                    line: 0,
17196                    character: 22,
17197                },
17198            },
17199            new_text: "Some(2)".to_string(),
17200        })),
17201        additional_text_edits: Some(vec![lsp::TextEdit {
17202            range: lsp::Range {
17203                start: lsp::Position {
17204                    line: 0,
17205                    character: 20,
17206                },
17207                end: lsp::Position {
17208                    line: 0,
17209                    character: 22,
17210                },
17211            },
17212            new_text: "".to_string(),
17213        }]),
17214        ..Default::default()
17215    };
17216
17217    let closure_completion_item = completion_item.clone();
17218    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17219        let task_completion_item = closure_completion_item.clone();
17220        async move {
17221            Ok(Some(lsp::CompletionResponse::Array(vec![
17222                task_completion_item,
17223            ])))
17224        }
17225    });
17226
17227    request.next().await;
17228
17229    cx.condition(|editor, _| editor.context_menu_visible())
17230        .await;
17231    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17232        editor
17233            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17234            .unwrap()
17235    });
17236    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17237
17238    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17239        let task_completion_item = completion_item.clone();
17240        async move { Ok(task_completion_item) }
17241    })
17242    .next()
17243    .await
17244    .unwrap();
17245    apply_additional_edits.await.unwrap();
17246    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17247}
17248
17249#[gpui::test]
17250async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17251    init_test(cx, |_| {});
17252
17253    let mut cx = EditorLspTestContext::new_rust(
17254        lsp::ServerCapabilities {
17255            completion_provider: Some(lsp::CompletionOptions {
17256                trigger_characters: Some(vec![".".to_string()]),
17257                resolve_provider: Some(true),
17258                ..Default::default()
17259            }),
17260            ..Default::default()
17261        },
17262        cx,
17263    )
17264    .await;
17265
17266    cx.set_state("fn main() { let a = 2ˇ; }");
17267    cx.simulate_keystroke(".");
17268
17269    let item1 = lsp::CompletionItem {
17270        label: "method id()".to_string(),
17271        filter_text: Some("id".to_string()),
17272        detail: None,
17273        documentation: None,
17274        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17275            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17276            new_text: ".id".to_string(),
17277        })),
17278        ..lsp::CompletionItem::default()
17279    };
17280
17281    let item2 = lsp::CompletionItem {
17282        label: "other".to_string(),
17283        filter_text: Some("other".to_string()),
17284        detail: None,
17285        documentation: None,
17286        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17287            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17288            new_text: ".other".to_string(),
17289        })),
17290        ..lsp::CompletionItem::default()
17291    };
17292
17293    let item1 = item1.clone();
17294    cx.set_request_handler::<lsp::request::Completion, _, _>({
17295        let item1 = item1.clone();
17296        move |_, _, _| {
17297            let item1 = item1.clone();
17298            let item2 = item2.clone();
17299            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17300        }
17301    })
17302    .next()
17303    .await;
17304
17305    cx.condition(|editor, _| editor.context_menu_visible())
17306        .await;
17307    cx.update_editor(|editor, _, _| {
17308        let context_menu = editor.context_menu.borrow_mut();
17309        let context_menu = context_menu
17310            .as_ref()
17311            .expect("Should have the context menu deployed");
17312        match context_menu {
17313            CodeContextMenu::Completions(completions_menu) => {
17314                let completions = completions_menu.completions.borrow_mut();
17315                assert_eq!(
17316                    completions
17317                        .iter()
17318                        .map(|completion| &completion.label.text)
17319                        .collect::<Vec<_>>(),
17320                    vec!["method id()", "other"]
17321                )
17322            }
17323            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17324        }
17325    });
17326
17327    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17328        let item1 = item1.clone();
17329        move |_, item_to_resolve, _| {
17330            let item1 = item1.clone();
17331            async move {
17332                if item1 == item_to_resolve {
17333                    Ok(lsp::CompletionItem {
17334                        label: "method id()".to_string(),
17335                        filter_text: Some("id".to_string()),
17336                        detail: Some("Now resolved!".to_string()),
17337                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17338                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17339                            range: lsp::Range::new(
17340                                lsp::Position::new(0, 22),
17341                                lsp::Position::new(0, 22),
17342                            ),
17343                            new_text: ".id".to_string(),
17344                        })),
17345                        ..lsp::CompletionItem::default()
17346                    })
17347                } else {
17348                    Ok(item_to_resolve)
17349                }
17350            }
17351        }
17352    })
17353    .next()
17354    .await
17355    .unwrap();
17356    cx.run_until_parked();
17357
17358    cx.update_editor(|editor, window, cx| {
17359        editor.context_menu_next(&Default::default(), window, cx);
17360    });
17361
17362    cx.update_editor(|editor, _, _| {
17363        let context_menu = editor.context_menu.borrow_mut();
17364        let context_menu = context_menu
17365            .as_ref()
17366            .expect("Should have the context menu deployed");
17367        match context_menu {
17368            CodeContextMenu::Completions(completions_menu) => {
17369                let completions = completions_menu.completions.borrow_mut();
17370                assert_eq!(
17371                    completions
17372                        .iter()
17373                        .map(|completion| &completion.label.text)
17374                        .collect::<Vec<_>>(),
17375                    vec!["method id() Now resolved!", "other"],
17376                    "Should update first completion label, but not second as the filter text did not match."
17377                );
17378            }
17379            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17380        }
17381    });
17382}
17383
17384#[gpui::test]
17385async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17386    init_test(cx, |_| {});
17387    let mut cx = EditorLspTestContext::new_rust(
17388        lsp::ServerCapabilities {
17389            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17390            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17391            completion_provider: Some(lsp::CompletionOptions {
17392                resolve_provider: Some(true),
17393                ..Default::default()
17394            }),
17395            ..Default::default()
17396        },
17397        cx,
17398    )
17399    .await;
17400    cx.set_state(indoc! {"
17401        struct TestStruct {
17402            field: i32
17403        }
17404
17405        fn mainˇ() {
17406            let unused_var = 42;
17407            let test_struct = TestStruct { field: 42 };
17408        }
17409    "});
17410    let symbol_range = cx.lsp_range(indoc! {"
17411        struct TestStruct {
17412            field: i32
17413        }
17414
17415        «fn main»() {
17416            let unused_var = 42;
17417            let test_struct = TestStruct { field: 42 };
17418        }
17419    "});
17420    let mut hover_requests =
17421        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17422            Ok(Some(lsp::Hover {
17423                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17424                    kind: lsp::MarkupKind::Markdown,
17425                    value: "Function documentation".to_string(),
17426                }),
17427                range: Some(symbol_range),
17428            }))
17429        });
17430
17431    // Case 1: Test that code action menu hide hover popover
17432    cx.dispatch_action(Hover);
17433    hover_requests.next().await;
17434    cx.condition(|editor, _| editor.hover_state.visible()).await;
17435    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17436        move |_, _, _| async move {
17437            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17438                lsp::CodeAction {
17439                    title: "Remove unused variable".to_string(),
17440                    kind: Some(CodeActionKind::QUICKFIX),
17441                    edit: Some(lsp::WorkspaceEdit {
17442                        changes: Some(
17443                            [(
17444                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17445                                vec![lsp::TextEdit {
17446                                    range: lsp::Range::new(
17447                                        lsp::Position::new(5, 4),
17448                                        lsp::Position::new(5, 27),
17449                                    ),
17450                                    new_text: "".to_string(),
17451                                }],
17452                            )]
17453                            .into_iter()
17454                            .collect(),
17455                        ),
17456                        ..Default::default()
17457                    }),
17458                    ..Default::default()
17459                },
17460            )]))
17461        },
17462    );
17463    cx.update_editor(|editor, window, cx| {
17464        editor.toggle_code_actions(
17465            &ToggleCodeActions {
17466                deployed_from: None,
17467                quick_launch: false,
17468            },
17469            window,
17470            cx,
17471        );
17472    });
17473    code_action_requests.next().await;
17474    cx.run_until_parked();
17475    cx.condition(|editor, _| editor.context_menu_visible())
17476        .await;
17477    cx.update_editor(|editor, _, _| {
17478        assert!(
17479            !editor.hover_state.visible(),
17480            "Hover popover should be hidden when code action menu is shown"
17481        );
17482        // Hide code actions
17483        editor.context_menu.take();
17484    });
17485
17486    // Case 2: Test that code completions hide hover popover
17487    cx.dispatch_action(Hover);
17488    hover_requests.next().await;
17489    cx.condition(|editor, _| editor.hover_state.visible()).await;
17490    let counter = Arc::new(AtomicUsize::new(0));
17491    let mut completion_requests =
17492        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17493            let counter = counter.clone();
17494            async move {
17495                counter.fetch_add(1, atomic::Ordering::Release);
17496                Ok(Some(lsp::CompletionResponse::Array(vec![
17497                    lsp::CompletionItem {
17498                        label: "main".into(),
17499                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17500                        detail: Some("() -> ()".to_string()),
17501                        ..Default::default()
17502                    },
17503                    lsp::CompletionItem {
17504                        label: "TestStruct".into(),
17505                        kind: Some(lsp::CompletionItemKind::STRUCT),
17506                        detail: Some("struct TestStruct".to_string()),
17507                        ..Default::default()
17508                    },
17509                ])))
17510            }
17511        });
17512    cx.update_editor(|editor, window, cx| {
17513        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17514    });
17515    completion_requests.next().await;
17516    cx.condition(|editor, _| editor.context_menu_visible())
17517        .await;
17518    cx.update_editor(|editor, _, _| {
17519        assert!(
17520            !editor.hover_state.visible(),
17521            "Hover popover should be hidden when completion menu is shown"
17522        );
17523    });
17524}
17525
17526#[gpui::test]
17527async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17528    init_test(cx, |_| {});
17529
17530    let mut cx = EditorLspTestContext::new_rust(
17531        lsp::ServerCapabilities {
17532            completion_provider: Some(lsp::CompletionOptions {
17533                trigger_characters: Some(vec![".".to_string()]),
17534                resolve_provider: Some(true),
17535                ..Default::default()
17536            }),
17537            ..Default::default()
17538        },
17539        cx,
17540    )
17541    .await;
17542
17543    cx.set_state("fn main() { let a = 2ˇ; }");
17544    cx.simulate_keystroke(".");
17545
17546    let unresolved_item_1 = lsp::CompletionItem {
17547        label: "id".to_string(),
17548        filter_text: Some("id".to_string()),
17549        detail: None,
17550        documentation: None,
17551        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17552            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17553            new_text: ".id".to_string(),
17554        })),
17555        ..lsp::CompletionItem::default()
17556    };
17557    let resolved_item_1 = lsp::CompletionItem {
17558        additional_text_edits: Some(vec![lsp::TextEdit {
17559            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17560            new_text: "!!".to_string(),
17561        }]),
17562        ..unresolved_item_1.clone()
17563    };
17564    let unresolved_item_2 = lsp::CompletionItem {
17565        label: "other".to_string(),
17566        filter_text: Some("other".to_string()),
17567        detail: None,
17568        documentation: None,
17569        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17570            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17571            new_text: ".other".to_string(),
17572        })),
17573        ..lsp::CompletionItem::default()
17574    };
17575    let resolved_item_2 = lsp::CompletionItem {
17576        additional_text_edits: Some(vec![lsp::TextEdit {
17577            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17578            new_text: "??".to_string(),
17579        }]),
17580        ..unresolved_item_2.clone()
17581    };
17582
17583    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17584    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17585    cx.lsp
17586        .server
17587        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17588            let unresolved_item_1 = unresolved_item_1.clone();
17589            let resolved_item_1 = resolved_item_1.clone();
17590            let unresolved_item_2 = unresolved_item_2.clone();
17591            let resolved_item_2 = resolved_item_2.clone();
17592            let resolve_requests_1 = resolve_requests_1.clone();
17593            let resolve_requests_2 = resolve_requests_2.clone();
17594            move |unresolved_request, _| {
17595                let unresolved_item_1 = unresolved_item_1.clone();
17596                let resolved_item_1 = resolved_item_1.clone();
17597                let unresolved_item_2 = unresolved_item_2.clone();
17598                let resolved_item_2 = resolved_item_2.clone();
17599                let resolve_requests_1 = resolve_requests_1.clone();
17600                let resolve_requests_2 = resolve_requests_2.clone();
17601                async move {
17602                    if unresolved_request == unresolved_item_1 {
17603                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17604                        Ok(resolved_item_1.clone())
17605                    } else if unresolved_request == unresolved_item_2 {
17606                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17607                        Ok(resolved_item_2.clone())
17608                    } else {
17609                        panic!("Unexpected completion item {unresolved_request:?}")
17610                    }
17611                }
17612            }
17613        })
17614        .detach();
17615
17616    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17617        let unresolved_item_1 = unresolved_item_1.clone();
17618        let unresolved_item_2 = unresolved_item_2.clone();
17619        async move {
17620            Ok(Some(lsp::CompletionResponse::Array(vec![
17621                unresolved_item_1,
17622                unresolved_item_2,
17623            ])))
17624        }
17625    })
17626    .next()
17627    .await;
17628
17629    cx.condition(|editor, _| editor.context_menu_visible())
17630        .await;
17631    cx.update_editor(|editor, _, _| {
17632        let context_menu = editor.context_menu.borrow_mut();
17633        let context_menu = context_menu
17634            .as_ref()
17635            .expect("Should have the context menu deployed");
17636        match context_menu {
17637            CodeContextMenu::Completions(completions_menu) => {
17638                let completions = completions_menu.completions.borrow_mut();
17639                assert_eq!(
17640                    completions
17641                        .iter()
17642                        .map(|completion| &completion.label.text)
17643                        .collect::<Vec<_>>(),
17644                    vec!["id", "other"]
17645                )
17646            }
17647            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17648        }
17649    });
17650    cx.run_until_parked();
17651
17652    cx.update_editor(|editor, window, cx| {
17653        editor.context_menu_next(&ContextMenuNext, window, cx);
17654    });
17655    cx.run_until_parked();
17656    cx.update_editor(|editor, window, cx| {
17657        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17658    });
17659    cx.run_until_parked();
17660    cx.update_editor(|editor, window, cx| {
17661        editor.context_menu_next(&ContextMenuNext, window, cx);
17662    });
17663    cx.run_until_parked();
17664    cx.update_editor(|editor, window, cx| {
17665        editor
17666            .compose_completion(&ComposeCompletion::default(), window, cx)
17667            .expect("No task returned")
17668    })
17669    .await
17670    .expect("Completion failed");
17671    cx.run_until_parked();
17672
17673    cx.update_editor(|editor, _, cx| {
17674        assert_eq!(
17675            resolve_requests_1.load(atomic::Ordering::Acquire),
17676            1,
17677            "Should always resolve once despite multiple selections"
17678        );
17679        assert_eq!(
17680            resolve_requests_2.load(atomic::Ordering::Acquire),
17681            1,
17682            "Should always resolve once after multiple selections and applying the completion"
17683        );
17684        assert_eq!(
17685            editor.text(cx),
17686            "fn main() { let a = ??.other; }",
17687            "Should use resolved data when applying the completion"
17688        );
17689    });
17690}
17691
17692#[gpui::test]
17693async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17694    init_test(cx, |_| {});
17695
17696    let item_0 = lsp::CompletionItem {
17697        label: "abs".into(),
17698        insert_text: Some("abs".into()),
17699        data: Some(json!({ "very": "special"})),
17700        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17701        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17702            lsp::InsertReplaceEdit {
17703                new_text: "abs".to_string(),
17704                insert: lsp::Range::default(),
17705                replace: lsp::Range::default(),
17706            },
17707        )),
17708        ..lsp::CompletionItem::default()
17709    };
17710    let items = iter::once(item_0.clone())
17711        .chain((11..51).map(|i| lsp::CompletionItem {
17712            label: format!("item_{}", i),
17713            insert_text: Some(format!("item_{}", i)),
17714            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17715            ..lsp::CompletionItem::default()
17716        }))
17717        .collect::<Vec<_>>();
17718
17719    let default_commit_characters = vec!["?".to_string()];
17720    let default_data = json!({ "default": "data"});
17721    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17722    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17723    let default_edit_range = lsp::Range {
17724        start: lsp::Position {
17725            line: 0,
17726            character: 5,
17727        },
17728        end: lsp::Position {
17729            line: 0,
17730            character: 5,
17731        },
17732    };
17733
17734    let mut cx = EditorLspTestContext::new_rust(
17735        lsp::ServerCapabilities {
17736            completion_provider: Some(lsp::CompletionOptions {
17737                trigger_characters: Some(vec![".".to_string()]),
17738                resolve_provider: Some(true),
17739                ..Default::default()
17740            }),
17741            ..Default::default()
17742        },
17743        cx,
17744    )
17745    .await;
17746
17747    cx.set_state("fn main() { let a = 2ˇ; }");
17748    cx.simulate_keystroke(".");
17749
17750    let completion_data = default_data.clone();
17751    let completion_characters = default_commit_characters.clone();
17752    let completion_items = items.clone();
17753    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17754        let default_data = completion_data.clone();
17755        let default_commit_characters = completion_characters.clone();
17756        let items = completion_items.clone();
17757        async move {
17758            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17759                items,
17760                item_defaults: Some(lsp::CompletionListItemDefaults {
17761                    data: Some(default_data.clone()),
17762                    commit_characters: Some(default_commit_characters.clone()),
17763                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17764                        default_edit_range,
17765                    )),
17766                    insert_text_format: Some(default_insert_text_format),
17767                    insert_text_mode: Some(default_insert_text_mode),
17768                }),
17769                ..lsp::CompletionList::default()
17770            })))
17771        }
17772    })
17773    .next()
17774    .await;
17775
17776    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17777    cx.lsp
17778        .server
17779        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17780            let closure_resolved_items = resolved_items.clone();
17781            move |item_to_resolve, _| {
17782                let closure_resolved_items = closure_resolved_items.clone();
17783                async move {
17784                    closure_resolved_items.lock().push(item_to_resolve.clone());
17785                    Ok(item_to_resolve)
17786                }
17787            }
17788        })
17789        .detach();
17790
17791    cx.condition(|editor, _| editor.context_menu_visible())
17792        .await;
17793    cx.run_until_parked();
17794    cx.update_editor(|editor, _, _| {
17795        let menu = editor.context_menu.borrow_mut();
17796        match menu.as_ref().expect("should have the completions menu") {
17797            CodeContextMenu::Completions(completions_menu) => {
17798                assert_eq!(
17799                    completions_menu
17800                        .entries
17801                        .borrow()
17802                        .iter()
17803                        .map(|mat| mat.string.clone())
17804                        .collect::<Vec<String>>(),
17805                    items
17806                        .iter()
17807                        .map(|completion| completion.label.clone())
17808                        .collect::<Vec<String>>()
17809                );
17810            }
17811            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17812        }
17813    });
17814    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17815    // with 4 from the end.
17816    assert_eq!(
17817        *resolved_items.lock(),
17818        [&items[0..16], &items[items.len() - 4..items.len()]]
17819            .concat()
17820            .iter()
17821            .cloned()
17822            .map(|mut item| {
17823                if item.data.is_none() {
17824                    item.data = Some(default_data.clone());
17825                }
17826                item
17827            })
17828            .collect::<Vec<lsp::CompletionItem>>(),
17829        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17830    );
17831    resolved_items.lock().clear();
17832
17833    cx.update_editor(|editor, window, cx| {
17834        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17835    });
17836    cx.run_until_parked();
17837    // Completions that have already been resolved are skipped.
17838    assert_eq!(
17839        *resolved_items.lock(),
17840        items[items.len() - 17..items.len() - 4]
17841            .iter()
17842            .cloned()
17843            .map(|mut item| {
17844                if item.data.is_none() {
17845                    item.data = Some(default_data.clone());
17846                }
17847                item
17848            })
17849            .collect::<Vec<lsp::CompletionItem>>()
17850    );
17851    resolved_items.lock().clear();
17852}
17853
17854#[gpui::test]
17855async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17856    init_test(cx, |_| {});
17857
17858    let mut cx = EditorLspTestContext::new(
17859        Language::new(
17860            LanguageConfig {
17861                matcher: LanguageMatcher {
17862                    path_suffixes: vec!["jsx".into()],
17863                    ..Default::default()
17864                },
17865                overrides: [(
17866                    "element".into(),
17867                    LanguageConfigOverride {
17868                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
17869                        ..Default::default()
17870                    },
17871                )]
17872                .into_iter()
17873                .collect(),
17874                ..Default::default()
17875            },
17876            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17877        )
17878        .with_override_query("(jsx_self_closing_element) @element")
17879        .unwrap(),
17880        lsp::ServerCapabilities {
17881            completion_provider: Some(lsp::CompletionOptions {
17882                trigger_characters: Some(vec![":".to_string()]),
17883                ..Default::default()
17884            }),
17885            ..Default::default()
17886        },
17887        cx,
17888    )
17889    .await;
17890
17891    cx.lsp
17892        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17893            Ok(Some(lsp::CompletionResponse::Array(vec![
17894                lsp::CompletionItem {
17895                    label: "bg-blue".into(),
17896                    ..Default::default()
17897                },
17898                lsp::CompletionItem {
17899                    label: "bg-red".into(),
17900                    ..Default::default()
17901                },
17902                lsp::CompletionItem {
17903                    label: "bg-yellow".into(),
17904                    ..Default::default()
17905                },
17906            ])))
17907        });
17908
17909    cx.set_state(r#"<p class="bgˇ" />"#);
17910
17911    // Trigger completion when typing a dash, because the dash is an extra
17912    // word character in the 'element' scope, which contains the cursor.
17913    cx.simulate_keystroke("-");
17914    cx.executor().run_until_parked();
17915    cx.update_editor(|editor, _, _| {
17916        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17917        {
17918            assert_eq!(
17919                completion_menu_entries(menu),
17920                &["bg-blue", "bg-red", "bg-yellow"]
17921            );
17922        } else {
17923            panic!("expected completion menu to be open");
17924        }
17925    });
17926
17927    cx.simulate_keystroke("l");
17928    cx.executor().run_until_parked();
17929    cx.update_editor(|editor, _, _| {
17930        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17931        {
17932            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
17933        } else {
17934            panic!("expected completion menu to be open");
17935        }
17936    });
17937
17938    // When filtering completions, consider the character after the '-' to
17939    // be the start of a subword.
17940    cx.set_state(r#"<p class="yelˇ" />"#);
17941    cx.simulate_keystroke("l");
17942    cx.executor().run_until_parked();
17943    cx.update_editor(|editor, _, _| {
17944        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17945        {
17946            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
17947        } else {
17948            panic!("expected completion menu to be open");
17949        }
17950    });
17951}
17952
17953fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
17954    let entries = menu.entries.borrow();
17955    entries.iter().map(|mat| mat.string.clone()).collect()
17956}
17957
17958#[gpui::test]
17959async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
17960    init_test(cx, |settings| {
17961        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
17962            Formatter::Prettier,
17963        )))
17964    });
17965
17966    let fs = FakeFs::new(cx.executor());
17967    fs.insert_file(path!("/file.ts"), Default::default()).await;
17968
17969    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
17970    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17971
17972    language_registry.add(Arc::new(Language::new(
17973        LanguageConfig {
17974            name: "TypeScript".into(),
17975            matcher: LanguageMatcher {
17976                path_suffixes: vec!["ts".to_string()],
17977                ..Default::default()
17978            },
17979            ..Default::default()
17980        },
17981        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
17982    )));
17983    update_test_language_settings(cx, |settings| {
17984        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
17985    });
17986
17987    let test_plugin = "test_plugin";
17988    let _ = language_registry.register_fake_lsp(
17989        "TypeScript",
17990        FakeLspAdapter {
17991            prettier_plugins: vec![test_plugin],
17992            ..Default::default()
17993        },
17994    );
17995
17996    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
17997    let buffer = project
17998        .update(cx, |project, cx| {
17999            project.open_local_buffer(path!("/file.ts"), cx)
18000        })
18001        .await
18002        .unwrap();
18003
18004    let buffer_text = "one\ntwo\nthree\n";
18005    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18006    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18007    editor.update_in(cx, |editor, window, cx| {
18008        editor.set_text(buffer_text, window, cx)
18009    });
18010
18011    editor
18012        .update_in(cx, |editor, window, cx| {
18013            editor.perform_format(
18014                project.clone(),
18015                FormatTrigger::Manual,
18016                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18017                window,
18018                cx,
18019            )
18020        })
18021        .unwrap()
18022        .await;
18023    assert_eq!(
18024        editor.update(cx, |editor, cx| editor.text(cx)),
18025        buffer_text.to_string() + prettier_format_suffix,
18026        "Test prettier formatting was not applied to the original buffer text",
18027    );
18028
18029    update_test_language_settings(cx, |settings| {
18030        settings.defaults.formatter = Some(SelectedFormatter::Auto)
18031    });
18032    let format = editor.update_in(cx, |editor, window, cx| {
18033        editor.perform_format(
18034            project.clone(),
18035            FormatTrigger::Manual,
18036            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18037            window,
18038            cx,
18039        )
18040    });
18041    format.await.unwrap();
18042    assert_eq!(
18043        editor.update(cx, |editor, cx| editor.text(cx)),
18044        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18045        "Autoformatting (via test prettier) was not applied to the original buffer text",
18046    );
18047}
18048
18049#[gpui::test]
18050async fn test_addition_reverts(cx: &mut TestAppContext) {
18051    init_test(cx, |_| {});
18052    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18053    let base_text = indoc! {r#"
18054        struct Row;
18055        struct Row1;
18056        struct Row2;
18057
18058        struct Row4;
18059        struct Row5;
18060        struct Row6;
18061
18062        struct Row8;
18063        struct Row9;
18064        struct Row10;"#};
18065
18066    // When addition hunks are not adjacent to carets, no hunk revert is performed
18067    assert_hunk_revert(
18068        indoc! {r#"struct Row;
18069                   struct Row1;
18070                   struct Row1.1;
18071                   struct Row1.2;
18072                   struct Row2;ˇ
18073
18074                   struct Row4;
18075                   struct Row5;
18076                   struct Row6;
18077
18078                   struct Row8;
18079                   ˇstruct Row9;
18080                   struct Row9.1;
18081                   struct Row9.2;
18082                   struct Row9.3;
18083                   struct Row10;"#},
18084        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18085        indoc! {r#"struct Row;
18086                   struct Row1;
18087                   struct Row1.1;
18088                   struct Row1.2;
18089                   struct Row2;ˇ
18090
18091                   struct Row4;
18092                   struct Row5;
18093                   struct Row6;
18094
18095                   struct Row8;
18096                   ˇstruct Row9;
18097                   struct Row9.1;
18098                   struct Row9.2;
18099                   struct Row9.3;
18100                   struct Row10;"#},
18101        base_text,
18102        &mut cx,
18103    );
18104    // Same for selections
18105    assert_hunk_revert(
18106        indoc! {r#"struct Row;
18107                   struct Row1;
18108                   struct Row2;
18109                   struct Row2.1;
18110                   struct Row2.2;
18111                   «ˇ
18112                   struct Row4;
18113                   struct» Row5;
18114                   «struct Row6;
18115                   ˇ»
18116                   struct Row9.1;
18117                   struct Row9.2;
18118                   struct Row9.3;
18119                   struct Row8;
18120                   struct Row9;
18121                   struct Row10;"#},
18122        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18123        indoc! {r#"struct Row;
18124                   struct Row1;
18125                   struct Row2;
18126                   struct Row2.1;
18127                   struct Row2.2;
18128                   «ˇ
18129                   struct Row4;
18130                   struct» Row5;
18131                   «struct Row6;
18132                   ˇ»
18133                   struct Row9.1;
18134                   struct Row9.2;
18135                   struct Row9.3;
18136                   struct Row8;
18137                   struct Row9;
18138                   struct Row10;"#},
18139        base_text,
18140        &mut cx,
18141    );
18142
18143    // When carets and selections intersect the addition hunks, those are reverted.
18144    // Adjacent carets got merged.
18145    assert_hunk_revert(
18146        indoc! {r#"struct Row;
18147                   ˇ// something on the top
18148                   struct Row1;
18149                   struct Row2;
18150                   struct Roˇw3.1;
18151                   struct Row2.2;
18152                   struct Row2.3;ˇ
18153
18154                   struct Row4;
18155                   struct ˇRow5.1;
18156                   struct Row5.2;
18157                   struct «Rowˇ»5.3;
18158                   struct Row5;
18159                   struct Row6;
18160                   ˇ
18161                   struct Row9.1;
18162                   struct «Rowˇ»9.2;
18163                   struct «ˇRow»9.3;
18164                   struct Row8;
18165                   struct Row9;
18166                   «ˇ// something on bottom»
18167                   struct Row10;"#},
18168        vec![
18169            DiffHunkStatusKind::Added,
18170            DiffHunkStatusKind::Added,
18171            DiffHunkStatusKind::Added,
18172            DiffHunkStatusKind::Added,
18173            DiffHunkStatusKind::Added,
18174        ],
18175        indoc! {r#"struct Row;
18176                   ˇstruct Row1;
18177                   struct Row2;
18178                   ˇ
18179                   struct Row4;
18180                   ˇstruct Row5;
18181                   struct Row6;
18182                   ˇ
18183                   ˇstruct Row8;
18184                   struct Row9;
18185                   ˇstruct Row10;"#},
18186        base_text,
18187        &mut cx,
18188    );
18189}
18190
18191#[gpui::test]
18192async fn test_modification_reverts(cx: &mut TestAppContext) {
18193    init_test(cx, |_| {});
18194    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18195    let base_text = indoc! {r#"
18196        struct Row;
18197        struct Row1;
18198        struct Row2;
18199
18200        struct Row4;
18201        struct Row5;
18202        struct Row6;
18203
18204        struct Row8;
18205        struct Row9;
18206        struct Row10;"#};
18207
18208    // Modification hunks behave the same as the addition ones.
18209    assert_hunk_revert(
18210        indoc! {r#"struct Row;
18211                   struct Row1;
18212                   struct Row33;
18213                   ˇ
18214                   struct Row4;
18215                   struct Row5;
18216                   struct Row6;
18217                   ˇ
18218                   struct Row99;
18219                   struct Row9;
18220                   struct Row10;"#},
18221        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18222        indoc! {r#"struct Row;
18223                   struct Row1;
18224                   struct Row33;
18225                   ˇ
18226                   struct Row4;
18227                   struct Row5;
18228                   struct Row6;
18229                   ˇ
18230                   struct Row99;
18231                   struct Row9;
18232                   struct Row10;"#},
18233        base_text,
18234        &mut cx,
18235    );
18236    assert_hunk_revert(
18237        indoc! {r#"struct Row;
18238                   struct Row1;
18239                   struct Row33;
18240                   «ˇ
18241                   struct Row4;
18242                   struct» Row5;
18243                   «struct Row6;
18244                   ˇ»
18245                   struct Row99;
18246                   struct Row9;
18247                   struct Row10;"#},
18248        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18249        indoc! {r#"struct Row;
18250                   struct Row1;
18251                   struct Row33;
18252                   «ˇ
18253                   struct Row4;
18254                   struct» Row5;
18255                   «struct Row6;
18256                   ˇ»
18257                   struct Row99;
18258                   struct Row9;
18259                   struct Row10;"#},
18260        base_text,
18261        &mut cx,
18262    );
18263
18264    assert_hunk_revert(
18265        indoc! {r#"ˇstruct Row1.1;
18266                   struct Row1;
18267                   «ˇstr»uct Row22;
18268
18269                   struct ˇRow44;
18270                   struct Row5;
18271                   struct «Rˇ»ow66;ˇ
18272
18273                   «struˇ»ct Row88;
18274                   struct Row9;
18275                   struct Row1011;ˇ"#},
18276        vec![
18277            DiffHunkStatusKind::Modified,
18278            DiffHunkStatusKind::Modified,
18279            DiffHunkStatusKind::Modified,
18280            DiffHunkStatusKind::Modified,
18281            DiffHunkStatusKind::Modified,
18282            DiffHunkStatusKind::Modified,
18283        ],
18284        indoc! {r#"struct Row;
18285                   ˇstruct Row1;
18286                   struct Row2;
18287                   ˇ
18288                   struct Row4;
18289                   ˇstruct Row5;
18290                   struct Row6;
18291                   ˇ
18292                   struct Row8;
18293                   ˇstruct Row9;
18294                   struct Row10;ˇ"#},
18295        base_text,
18296        &mut cx,
18297    );
18298}
18299
18300#[gpui::test]
18301async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18302    init_test(cx, |_| {});
18303    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18304    let base_text = indoc! {r#"
18305        one
18306
18307        two
18308        three
18309        "#};
18310
18311    cx.set_head_text(base_text);
18312    cx.set_state("\nˇ\n");
18313    cx.executor().run_until_parked();
18314    cx.update_editor(|editor, _window, cx| {
18315        editor.expand_selected_diff_hunks(cx);
18316    });
18317    cx.executor().run_until_parked();
18318    cx.update_editor(|editor, window, cx| {
18319        editor.backspace(&Default::default(), window, cx);
18320    });
18321    cx.run_until_parked();
18322    cx.assert_state_with_diff(
18323        indoc! {r#"
18324
18325        - two
18326        - threeˇ
18327        +
18328        "#}
18329        .to_string(),
18330    );
18331}
18332
18333#[gpui::test]
18334async fn test_deletion_reverts(cx: &mut TestAppContext) {
18335    init_test(cx, |_| {});
18336    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18337    let base_text = indoc! {r#"struct Row;
18338struct Row1;
18339struct Row2;
18340
18341struct Row4;
18342struct Row5;
18343struct Row6;
18344
18345struct Row8;
18346struct Row9;
18347struct Row10;"#};
18348
18349    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18350    assert_hunk_revert(
18351        indoc! {r#"struct Row;
18352                   struct Row2;
18353
18354                   ˇstruct Row4;
18355                   struct Row5;
18356                   struct Row6;
18357                   ˇ
18358                   struct Row8;
18359                   struct Row10;"#},
18360        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18361        indoc! {r#"struct Row;
18362                   struct Row2;
18363
18364                   ˇstruct Row4;
18365                   struct Row5;
18366                   struct Row6;
18367                   ˇ
18368                   struct Row8;
18369                   struct Row10;"#},
18370        base_text,
18371        &mut cx,
18372    );
18373    assert_hunk_revert(
18374        indoc! {r#"struct Row;
18375                   struct Row2;
18376
18377                   «ˇstruct Row4;
18378                   struct» Row5;
18379                   «struct Row6;
18380                   ˇ»
18381                   struct Row8;
18382                   struct Row10;"#},
18383        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18384        indoc! {r#"struct Row;
18385                   struct Row2;
18386
18387                   «ˇstruct Row4;
18388                   struct» Row5;
18389                   «struct Row6;
18390                   ˇ»
18391                   struct Row8;
18392                   struct Row10;"#},
18393        base_text,
18394        &mut cx,
18395    );
18396
18397    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18398    assert_hunk_revert(
18399        indoc! {r#"struct Row;
18400                   ˇstruct Row2;
18401
18402                   struct Row4;
18403                   struct Row5;
18404                   struct Row6;
18405
18406                   struct Row8;ˇ
18407                   struct Row10;"#},
18408        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18409        indoc! {r#"struct Row;
18410                   struct Row1;
18411                   ˇstruct Row2;
18412
18413                   struct Row4;
18414                   struct Row5;
18415                   struct Row6;
18416
18417                   struct Row8;ˇ
18418                   struct Row9;
18419                   struct Row10;"#},
18420        base_text,
18421        &mut cx,
18422    );
18423    assert_hunk_revert(
18424        indoc! {r#"struct Row;
18425                   struct Row2«ˇ;
18426                   struct Row4;
18427                   struct» Row5;
18428                   «struct Row6;
18429
18430                   struct Row8;ˇ»
18431                   struct Row10;"#},
18432        vec![
18433            DiffHunkStatusKind::Deleted,
18434            DiffHunkStatusKind::Deleted,
18435            DiffHunkStatusKind::Deleted,
18436        ],
18437        indoc! {r#"struct Row;
18438                   struct Row1;
18439                   struct Row2«ˇ;
18440
18441                   struct Row4;
18442                   struct» Row5;
18443                   «struct Row6;
18444
18445                   struct Row8;ˇ»
18446                   struct Row9;
18447                   struct Row10;"#},
18448        base_text,
18449        &mut cx,
18450    );
18451}
18452
18453#[gpui::test]
18454async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18455    init_test(cx, |_| {});
18456
18457    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18458    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18459    let base_text_3 =
18460        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18461
18462    let text_1 = edit_first_char_of_every_line(base_text_1);
18463    let text_2 = edit_first_char_of_every_line(base_text_2);
18464    let text_3 = edit_first_char_of_every_line(base_text_3);
18465
18466    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18467    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18468    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18469
18470    let multibuffer = cx.new(|cx| {
18471        let mut multibuffer = MultiBuffer::new(ReadWrite);
18472        multibuffer.push_excerpts(
18473            buffer_1.clone(),
18474            [
18475                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18476                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18477                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18478            ],
18479            cx,
18480        );
18481        multibuffer.push_excerpts(
18482            buffer_2.clone(),
18483            [
18484                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18485                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18486                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18487            ],
18488            cx,
18489        );
18490        multibuffer.push_excerpts(
18491            buffer_3.clone(),
18492            [
18493                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18494                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18495                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18496            ],
18497            cx,
18498        );
18499        multibuffer
18500    });
18501
18502    let fs = FakeFs::new(cx.executor());
18503    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18504    let (editor, cx) = cx
18505        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18506    editor.update_in(cx, |editor, _window, cx| {
18507        for (buffer, diff_base) in [
18508            (buffer_1.clone(), base_text_1),
18509            (buffer_2.clone(), base_text_2),
18510            (buffer_3.clone(), base_text_3),
18511        ] {
18512            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18513            editor
18514                .buffer
18515                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18516        }
18517    });
18518    cx.executor().run_until_parked();
18519
18520    editor.update_in(cx, |editor, window, cx| {
18521        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}");
18522        editor.select_all(&SelectAll, window, cx);
18523        editor.git_restore(&Default::default(), window, cx);
18524    });
18525    cx.executor().run_until_parked();
18526
18527    // When all ranges are selected, all buffer hunks are reverted.
18528    editor.update(cx, |editor, cx| {
18529        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");
18530    });
18531    buffer_1.update(cx, |buffer, _| {
18532        assert_eq!(buffer.text(), base_text_1);
18533    });
18534    buffer_2.update(cx, |buffer, _| {
18535        assert_eq!(buffer.text(), base_text_2);
18536    });
18537    buffer_3.update(cx, |buffer, _| {
18538        assert_eq!(buffer.text(), base_text_3);
18539    });
18540
18541    editor.update_in(cx, |editor, window, cx| {
18542        editor.undo(&Default::default(), window, cx);
18543    });
18544
18545    editor.update_in(cx, |editor, window, cx| {
18546        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18547            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18548        });
18549        editor.git_restore(&Default::default(), window, cx);
18550    });
18551
18552    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18553    // but not affect buffer_2 and its related excerpts.
18554    editor.update(cx, |editor, cx| {
18555        assert_eq!(
18556            editor.text(cx),
18557            "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}"
18558        );
18559    });
18560    buffer_1.update(cx, |buffer, _| {
18561        assert_eq!(buffer.text(), base_text_1);
18562    });
18563    buffer_2.update(cx, |buffer, _| {
18564        assert_eq!(
18565            buffer.text(),
18566            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18567        );
18568    });
18569    buffer_3.update(cx, |buffer, _| {
18570        assert_eq!(
18571            buffer.text(),
18572            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18573        );
18574    });
18575
18576    fn edit_first_char_of_every_line(text: &str) -> String {
18577        text.split('\n')
18578            .map(|line| format!("X{}", &line[1..]))
18579            .collect::<Vec<_>>()
18580            .join("\n")
18581    }
18582}
18583
18584#[gpui::test]
18585async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18586    init_test(cx, |_| {});
18587
18588    let cols = 4;
18589    let rows = 10;
18590    let sample_text_1 = sample_text(rows, cols, 'a');
18591    assert_eq!(
18592        sample_text_1,
18593        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18594    );
18595    let sample_text_2 = sample_text(rows, cols, 'l');
18596    assert_eq!(
18597        sample_text_2,
18598        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18599    );
18600    let sample_text_3 = sample_text(rows, cols, 'v');
18601    assert_eq!(
18602        sample_text_3,
18603        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18604    );
18605
18606    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18607    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18608    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18609
18610    let multi_buffer = cx.new(|cx| {
18611        let mut multibuffer = MultiBuffer::new(ReadWrite);
18612        multibuffer.push_excerpts(
18613            buffer_1.clone(),
18614            [
18615                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18616                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18617                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18618            ],
18619            cx,
18620        );
18621        multibuffer.push_excerpts(
18622            buffer_2.clone(),
18623            [
18624                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18625                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18626                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18627            ],
18628            cx,
18629        );
18630        multibuffer.push_excerpts(
18631            buffer_3.clone(),
18632            [
18633                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18634                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18635                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18636            ],
18637            cx,
18638        );
18639        multibuffer
18640    });
18641
18642    let fs = FakeFs::new(cx.executor());
18643    fs.insert_tree(
18644        "/a",
18645        json!({
18646            "main.rs": sample_text_1,
18647            "other.rs": sample_text_2,
18648            "lib.rs": sample_text_3,
18649        }),
18650    )
18651    .await;
18652    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18653    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18654    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18655    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18656        Editor::new(
18657            EditorMode::full(),
18658            multi_buffer,
18659            Some(project.clone()),
18660            window,
18661            cx,
18662        )
18663    });
18664    let multibuffer_item_id = workspace
18665        .update(cx, |workspace, window, cx| {
18666            assert!(
18667                workspace.active_item(cx).is_none(),
18668                "active item should be None before the first item is added"
18669            );
18670            workspace.add_item_to_active_pane(
18671                Box::new(multi_buffer_editor.clone()),
18672                None,
18673                true,
18674                window,
18675                cx,
18676            );
18677            let active_item = workspace
18678                .active_item(cx)
18679                .expect("should have an active item after adding the multi buffer");
18680            assert!(
18681                !active_item.is_singleton(cx),
18682                "A multi buffer was expected to active after adding"
18683            );
18684            active_item.item_id()
18685        })
18686        .unwrap();
18687    cx.executor().run_until_parked();
18688
18689    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18690        editor.change_selections(
18691            SelectionEffects::scroll(Autoscroll::Next),
18692            window,
18693            cx,
18694            |s| s.select_ranges(Some(1..2)),
18695        );
18696        editor.open_excerpts(&OpenExcerpts, window, cx);
18697    });
18698    cx.executor().run_until_parked();
18699    let first_item_id = workspace
18700        .update(cx, |workspace, window, cx| {
18701            let active_item = workspace
18702                .active_item(cx)
18703                .expect("should have an active item after navigating into the 1st buffer");
18704            let first_item_id = active_item.item_id();
18705            assert_ne!(
18706                first_item_id, multibuffer_item_id,
18707                "Should navigate into the 1st buffer and activate it"
18708            );
18709            assert!(
18710                active_item.is_singleton(cx),
18711                "New active item should be a singleton buffer"
18712            );
18713            assert_eq!(
18714                active_item
18715                    .act_as::<Editor>(cx)
18716                    .expect("should have navigated into an editor for the 1st buffer")
18717                    .read(cx)
18718                    .text(cx),
18719                sample_text_1
18720            );
18721
18722            workspace
18723                .go_back(workspace.active_pane().downgrade(), window, cx)
18724                .detach_and_log_err(cx);
18725
18726            first_item_id
18727        })
18728        .unwrap();
18729    cx.executor().run_until_parked();
18730    workspace
18731        .update(cx, |workspace, _, cx| {
18732            let active_item = workspace
18733                .active_item(cx)
18734                .expect("should have an active item after navigating back");
18735            assert_eq!(
18736                active_item.item_id(),
18737                multibuffer_item_id,
18738                "Should navigate back to the multi buffer"
18739            );
18740            assert!(!active_item.is_singleton(cx));
18741        })
18742        .unwrap();
18743
18744    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18745        editor.change_selections(
18746            SelectionEffects::scroll(Autoscroll::Next),
18747            window,
18748            cx,
18749            |s| s.select_ranges(Some(39..40)),
18750        );
18751        editor.open_excerpts(&OpenExcerpts, window, cx);
18752    });
18753    cx.executor().run_until_parked();
18754    let second_item_id = workspace
18755        .update(cx, |workspace, window, cx| {
18756            let active_item = workspace
18757                .active_item(cx)
18758                .expect("should have an active item after navigating into the 2nd buffer");
18759            let second_item_id = active_item.item_id();
18760            assert_ne!(
18761                second_item_id, multibuffer_item_id,
18762                "Should navigate away from the multibuffer"
18763            );
18764            assert_ne!(
18765                second_item_id, first_item_id,
18766                "Should navigate into the 2nd buffer and activate it"
18767            );
18768            assert!(
18769                active_item.is_singleton(cx),
18770                "New active item should be a singleton buffer"
18771            );
18772            assert_eq!(
18773                active_item
18774                    .act_as::<Editor>(cx)
18775                    .expect("should have navigated into an editor")
18776                    .read(cx)
18777                    .text(cx),
18778                sample_text_2
18779            );
18780
18781            workspace
18782                .go_back(workspace.active_pane().downgrade(), window, cx)
18783                .detach_and_log_err(cx);
18784
18785            second_item_id
18786        })
18787        .unwrap();
18788    cx.executor().run_until_parked();
18789    workspace
18790        .update(cx, |workspace, _, cx| {
18791            let active_item = workspace
18792                .active_item(cx)
18793                .expect("should have an active item after navigating back from the 2nd buffer");
18794            assert_eq!(
18795                active_item.item_id(),
18796                multibuffer_item_id,
18797                "Should navigate back from the 2nd buffer to the multi buffer"
18798            );
18799            assert!(!active_item.is_singleton(cx));
18800        })
18801        .unwrap();
18802
18803    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18804        editor.change_selections(
18805            SelectionEffects::scroll(Autoscroll::Next),
18806            window,
18807            cx,
18808            |s| s.select_ranges(Some(70..70)),
18809        );
18810        editor.open_excerpts(&OpenExcerpts, window, cx);
18811    });
18812    cx.executor().run_until_parked();
18813    workspace
18814        .update(cx, |workspace, window, cx| {
18815            let active_item = workspace
18816                .active_item(cx)
18817                .expect("should have an active item after navigating into the 3rd buffer");
18818            let third_item_id = active_item.item_id();
18819            assert_ne!(
18820                third_item_id, multibuffer_item_id,
18821                "Should navigate into the 3rd buffer and activate it"
18822            );
18823            assert_ne!(third_item_id, first_item_id);
18824            assert_ne!(third_item_id, second_item_id);
18825            assert!(
18826                active_item.is_singleton(cx),
18827                "New active item should be a singleton buffer"
18828            );
18829            assert_eq!(
18830                active_item
18831                    .act_as::<Editor>(cx)
18832                    .expect("should have navigated into an editor")
18833                    .read(cx)
18834                    .text(cx),
18835                sample_text_3
18836            );
18837
18838            workspace
18839                .go_back(workspace.active_pane().downgrade(), window, cx)
18840                .detach_and_log_err(cx);
18841        })
18842        .unwrap();
18843    cx.executor().run_until_parked();
18844    workspace
18845        .update(cx, |workspace, _, cx| {
18846            let active_item = workspace
18847                .active_item(cx)
18848                .expect("should have an active item after navigating back from the 3rd buffer");
18849            assert_eq!(
18850                active_item.item_id(),
18851                multibuffer_item_id,
18852                "Should navigate back from the 3rd buffer to the multi buffer"
18853            );
18854            assert!(!active_item.is_singleton(cx));
18855        })
18856        .unwrap();
18857}
18858
18859#[gpui::test]
18860async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18861    init_test(cx, |_| {});
18862
18863    let mut cx = EditorTestContext::new(cx).await;
18864
18865    let diff_base = r#"
18866        use some::mod;
18867
18868        const A: u32 = 42;
18869
18870        fn main() {
18871            println!("hello");
18872
18873            println!("world");
18874        }
18875        "#
18876    .unindent();
18877
18878    cx.set_state(
18879        &r#"
18880        use some::modified;
18881
18882        ˇ
18883        fn main() {
18884            println!("hello there");
18885
18886            println!("around the");
18887            println!("world");
18888        }
18889        "#
18890        .unindent(),
18891    );
18892
18893    cx.set_head_text(&diff_base);
18894    executor.run_until_parked();
18895
18896    cx.update_editor(|editor, window, cx| {
18897        editor.go_to_next_hunk(&GoToHunk, window, cx);
18898        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18899    });
18900    executor.run_until_parked();
18901    cx.assert_state_with_diff(
18902        r#"
18903          use some::modified;
18904
18905
18906          fn main() {
18907        -     println!("hello");
18908        + ˇ    println!("hello there");
18909
18910              println!("around the");
18911              println!("world");
18912          }
18913        "#
18914        .unindent(),
18915    );
18916
18917    cx.update_editor(|editor, window, cx| {
18918        for _ in 0..2 {
18919            editor.go_to_next_hunk(&GoToHunk, window, cx);
18920            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18921        }
18922    });
18923    executor.run_until_parked();
18924    cx.assert_state_with_diff(
18925        r#"
18926        - use some::mod;
18927        + ˇuse some::modified;
18928
18929
18930          fn main() {
18931        -     println!("hello");
18932        +     println!("hello there");
18933
18934        +     println!("around the");
18935              println!("world");
18936          }
18937        "#
18938        .unindent(),
18939    );
18940
18941    cx.update_editor(|editor, window, cx| {
18942        editor.go_to_next_hunk(&GoToHunk, window, cx);
18943        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18944    });
18945    executor.run_until_parked();
18946    cx.assert_state_with_diff(
18947        r#"
18948        - use some::mod;
18949        + use some::modified;
18950
18951        - const A: u32 = 42;
18952          ˇ
18953          fn main() {
18954        -     println!("hello");
18955        +     println!("hello there");
18956
18957        +     println!("around the");
18958              println!("world");
18959          }
18960        "#
18961        .unindent(),
18962    );
18963
18964    cx.update_editor(|editor, window, cx| {
18965        editor.cancel(&Cancel, window, cx);
18966    });
18967
18968    cx.assert_state_with_diff(
18969        r#"
18970          use some::modified;
18971
18972          ˇ
18973          fn main() {
18974              println!("hello there");
18975
18976              println!("around the");
18977              println!("world");
18978          }
18979        "#
18980        .unindent(),
18981    );
18982}
18983
18984#[gpui::test]
18985async fn test_diff_base_change_with_expanded_diff_hunks(
18986    executor: BackgroundExecutor,
18987    cx: &mut TestAppContext,
18988) {
18989    init_test(cx, |_| {});
18990
18991    let mut cx = EditorTestContext::new(cx).await;
18992
18993    let diff_base = r#"
18994        use some::mod1;
18995        use some::mod2;
18996
18997        const A: u32 = 42;
18998        const B: u32 = 42;
18999        const C: u32 = 42;
19000
19001        fn main() {
19002            println!("hello");
19003
19004            println!("world");
19005        }
19006        "#
19007    .unindent();
19008
19009    cx.set_state(
19010        &r#"
19011        use some::mod2;
19012
19013        const A: u32 = 42;
19014        const C: u32 = 42;
19015
19016        fn main(ˇ) {
19017            //println!("hello");
19018
19019            println!("world");
19020            //
19021            //
19022        }
19023        "#
19024        .unindent(),
19025    );
19026
19027    cx.set_head_text(&diff_base);
19028    executor.run_until_parked();
19029
19030    cx.update_editor(|editor, window, cx| {
19031        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19032    });
19033    executor.run_until_parked();
19034    cx.assert_state_with_diff(
19035        r#"
19036        - use some::mod1;
19037          use some::mod2;
19038
19039          const A: u32 = 42;
19040        - const B: u32 = 42;
19041          const C: u32 = 42;
19042
19043          fn main(ˇ) {
19044        -     println!("hello");
19045        +     //println!("hello");
19046
19047              println!("world");
19048        +     //
19049        +     //
19050          }
19051        "#
19052        .unindent(),
19053    );
19054
19055    cx.set_head_text("new diff base!");
19056    executor.run_until_parked();
19057    cx.assert_state_with_diff(
19058        r#"
19059        - new diff base!
19060        + use some::mod2;
19061        +
19062        + const A: u32 = 42;
19063        + const C: u32 = 42;
19064        +
19065        + fn main(ˇ) {
19066        +     //println!("hello");
19067        +
19068        +     println!("world");
19069        +     //
19070        +     //
19071        + }
19072        "#
19073        .unindent(),
19074    );
19075}
19076
19077#[gpui::test]
19078async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19079    init_test(cx, |_| {});
19080
19081    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19082    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19083    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19084    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19085    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19086    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19087
19088    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19089    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19090    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19091
19092    let multi_buffer = cx.new(|cx| {
19093        let mut multibuffer = MultiBuffer::new(ReadWrite);
19094        multibuffer.push_excerpts(
19095            buffer_1.clone(),
19096            [
19097                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19098                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19099                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19100            ],
19101            cx,
19102        );
19103        multibuffer.push_excerpts(
19104            buffer_2.clone(),
19105            [
19106                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19107                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19108                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19109            ],
19110            cx,
19111        );
19112        multibuffer.push_excerpts(
19113            buffer_3.clone(),
19114            [
19115                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19116                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19117                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19118            ],
19119            cx,
19120        );
19121        multibuffer
19122    });
19123
19124    let editor =
19125        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19126    editor
19127        .update(cx, |editor, _window, cx| {
19128            for (buffer, diff_base) in [
19129                (buffer_1.clone(), file_1_old),
19130                (buffer_2.clone(), file_2_old),
19131                (buffer_3.clone(), file_3_old),
19132            ] {
19133                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19134                editor
19135                    .buffer
19136                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19137            }
19138        })
19139        .unwrap();
19140
19141    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19142    cx.run_until_parked();
19143
19144    cx.assert_editor_state(
19145        &"
19146            ˇaaa
19147            ccc
19148            ddd
19149
19150            ggg
19151            hhh
19152
19153
19154            lll
19155            mmm
19156            NNN
19157
19158            qqq
19159            rrr
19160
19161            uuu
19162            111
19163            222
19164            333
19165
19166            666
19167            777
19168
19169            000
19170            !!!"
19171        .unindent(),
19172    );
19173
19174    cx.update_editor(|editor, window, cx| {
19175        editor.select_all(&SelectAll, window, cx);
19176        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19177    });
19178    cx.executor().run_until_parked();
19179
19180    cx.assert_state_with_diff(
19181        "
19182            «aaa
19183          - bbb
19184            ccc
19185            ddd
19186
19187            ggg
19188            hhh
19189
19190
19191            lll
19192            mmm
19193          - nnn
19194          + NNN
19195
19196            qqq
19197            rrr
19198
19199            uuu
19200            111
19201            222
19202            333
19203
19204          + 666
19205            777
19206
19207            000
19208            !!!ˇ»"
19209            .unindent(),
19210    );
19211}
19212
19213#[gpui::test]
19214async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19215    init_test(cx, |_| {});
19216
19217    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19218    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19219
19220    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19221    let multi_buffer = cx.new(|cx| {
19222        let mut multibuffer = MultiBuffer::new(ReadWrite);
19223        multibuffer.push_excerpts(
19224            buffer.clone(),
19225            [
19226                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19227                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19228                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19229            ],
19230            cx,
19231        );
19232        multibuffer
19233    });
19234
19235    let editor =
19236        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19237    editor
19238        .update(cx, |editor, _window, cx| {
19239            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19240            editor
19241                .buffer
19242                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19243        })
19244        .unwrap();
19245
19246    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19247    cx.run_until_parked();
19248
19249    cx.update_editor(|editor, window, cx| {
19250        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19251    });
19252    cx.executor().run_until_parked();
19253
19254    // When the start of a hunk coincides with the start of its excerpt,
19255    // the hunk is expanded. When the start of a hunk is earlier than
19256    // the start of its excerpt, the hunk is not expanded.
19257    cx.assert_state_with_diff(
19258        "
19259            ˇaaa
19260          - bbb
19261          + BBB
19262
19263          - ddd
19264          - eee
19265          + DDD
19266          + EEE
19267            fff
19268
19269            iii
19270        "
19271        .unindent(),
19272    );
19273}
19274
19275#[gpui::test]
19276async fn test_edits_around_expanded_insertion_hunks(
19277    executor: BackgroundExecutor,
19278    cx: &mut TestAppContext,
19279) {
19280    init_test(cx, |_| {});
19281
19282    let mut cx = EditorTestContext::new(cx).await;
19283
19284    let diff_base = r#"
19285        use some::mod1;
19286        use some::mod2;
19287
19288        const A: u32 = 42;
19289
19290        fn main() {
19291            println!("hello");
19292
19293            println!("world");
19294        }
19295        "#
19296    .unindent();
19297    executor.run_until_parked();
19298    cx.set_state(
19299        &r#"
19300        use some::mod1;
19301        use some::mod2;
19302
19303        const A: u32 = 42;
19304        const B: u32 = 42;
19305        const C: u32 = 42;
19306        ˇ
19307
19308        fn main() {
19309            println!("hello");
19310
19311            println!("world");
19312        }
19313        "#
19314        .unindent(),
19315    );
19316
19317    cx.set_head_text(&diff_base);
19318    executor.run_until_parked();
19319
19320    cx.update_editor(|editor, window, cx| {
19321        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19322    });
19323    executor.run_until_parked();
19324
19325    cx.assert_state_with_diff(
19326        r#"
19327        use some::mod1;
19328        use some::mod2;
19329
19330        const A: u32 = 42;
19331      + const B: u32 = 42;
19332      + const C: u32 = 42;
19333      + ˇ
19334
19335        fn main() {
19336            println!("hello");
19337
19338            println!("world");
19339        }
19340      "#
19341        .unindent(),
19342    );
19343
19344    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19345    executor.run_until_parked();
19346
19347    cx.assert_state_with_diff(
19348        r#"
19349        use some::mod1;
19350        use some::mod2;
19351
19352        const A: u32 = 42;
19353      + const B: u32 = 42;
19354      + const C: u32 = 42;
19355      + const D: u32 = 42;
19356      + ˇ
19357
19358        fn main() {
19359            println!("hello");
19360
19361            println!("world");
19362        }
19363      "#
19364        .unindent(),
19365    );
19366
19367    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19368    executor.run_until_parked();
19369
19370    cx.assert_state_with_diff(
19371        r#"
19372        use some::mod1;
19373        use some::mod2;
19374
19375        const A: u32 = 42;
19376      + const B: u32 = 42;
19377      + const C: u32 = 42;
19378      + const D: u32 = 42;
19379      + const E: u32 = 42;
19380      + ˇ
19381
19382        fn main() {
19383            println!("hello");
19384
19385            println!("world");
19386        }
19387      "#
19388        .unindent(),
19389    );
19390
19391    cx.update_editor(|editor, window, cx| {
19392        editor.delete_line(&DeleteLine, window, cx);
19393    });
19394    executor.run_until_parked();
19395
19396    cx.assert_state_with_diff(
19397        r#"
19398        use some::mod1;
19399        use some::mod2;
19400
19401        const A: u32 = 42;
19402      + const B: u32 = 42;
19403      + const C: u32 = 42;
19404      + const D: u32 = 42;
19405      + const E: u32 = 42;
19406        ˇ
19407        fn main() {
19408            println!("hello");
19409
19410            println!("world");
19411        }
19412      "#
19413        .unindent(),
19414    );
19415
19416    cx.update_editor(|editor, window, cx| {
19417        editor.move_up(&MoveUp, window, cx);
19418        editor.delete_line(&DeleteLine, window, cx);
19419        editor.move_up(&MoveUp, window, cx);
19420        editor.delete_line(&DeleteLine, window, cx);
19421        editor.move_up(&MoveUp, window, cx);
19422        editor.delete_line(&DeleteLine, window, cx);
19423    });
19424    executor.run_until_parked();
19425    cx.assert_state_with_diff(
19426        r#"
19427        use some::mod1;
19428        use some::mod2;
19429
19430        const A: u32 = 42;
19431      + const B: u32 = 42;
19432        ˇ
19433        fn main() {
19434            println!("hello");
19435
19436            println!("world");
19437        }
19438      "#
19439        .unindent(),
19440    );
19441
19442    cx.update_editor(|editor, window, cx| {
19443        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19444        editor.delete_line(&DeleteLine, window, cx);
19445    });
19446    executor.run_until_parked();
19447    cx.assert_state_with_diff(
19448        r#"
19449        ˇ
19450        fn main() {
19451            println!("hello");
19452
19453            println!("world");
19454        }
19455      "#
19456        .unindent(),
19457    );
19458}
19459
19460#[gpui::test]
19461async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19462    init_test(cx, |_| {});
19463
19464    let mut cx = EditorTestContext::new(cx).await;
19465    cx.set_head_text(indoc! { "
19466        one
19467        two
19468        three
19469        four
19470        five
19471        "
19472    });
19473    cx.set_state(indoc! { "
19474        one
19475        ˇthree
19476        five
19477    "});
19478    cx.run_until_parked();
19479    cx.update_editor(|editor, window, cx| {
19480        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19481    });
19482    cx.assert_state_with_diff(
19483        indoc! { "
19484        one
19485      - two
19486        ˇthree
19487      - four
19488        five
19489    "}
19490        .to_string(),
19491    );
19492    cx.update_editor(|editor, window, cx| {
19493        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19494    });
19495
19496    cx.assert_state_with_diff(
19497        indoc! { "
19498        one
19499        ˇthree
19500        five
19501    "}
19502        .to_string(),
19503    );
19504
19505    cx.set_state(indoc! { "
19506        one
19507        ˇTWO
19508        three
19509        four
19510        five
19511    "});
19512    cx.run_until_parked();
19513    cx.update_editor(|editor, window, cx| {
19514        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19515    });
19516
19517    cx.assert_state_with_diff(
19518        indoc! { "
19519            one
19520          - two
19521          + ˇTWO
19522            three
19523            four
19524            five
19525        "}
19526        .to_string(),
19527    );
19528    cx.update_editor(|editor, window, cx| {
19529        editor.move_up(&Default::default(), window, cx);
19530        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19531    });
19532    cx.assert_state_with_diff(
19533        indoc! { "
19534            one
19535            ˇTWO
19536            three
19537            four
19538            five
19539        "}
19540        .to_string(),
19541    );
19542}
19543
19544#[gpui::test]
19545async fn test_edits_around_expanded_deletion_hunks(
19546    executor: BackgroundExecutor,
19547    cx: &mut TestAppContext,
19548) {
19549    init_test(cx, |_| {});
19550
19551    let mut cx = EditorTestContext::new(cx).await;
19552
19553    let diff_base = r#"
19554        use some::mod1;
19555        use some::mod2;
19556
19557        const A: u32 = 42;
19558        const B: u32 = 42;
19559        const C: u32 = 42;
19560
19561
19562        fn main() {
19563            println!("hello");
19564
19565            println!("world");
19566        }
19567    "#
19568    .unindent();
19569    executor.run_until_parked();
19570    cx.set_state(
19571        &r#"
19572        use some::mod1;
19573        use some::mod2;
19574
19575        ˇconst B: u32 = 42;
19576        const C: u32 = 42;
19577
19578
19579        fn main() {
19580            println!("hello");
19581
19582            println!("world");
19583        }
19584        "#
19585        .unindent(),
19586    );
19587
19588    cx.set_head_text(&diff_base);
19589    executor.run_until_parked();
19590
19591    cx.update_editor(|editor, window, cx| {
19592        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19593    });
19594    executor.run_until_parked();
19595
19596    cx.assert_state_with_diff(
19597        r#"
19598        use some::mod1;
19599        use some::mod2;
19600
19601      - const A: u32 = 42;
19602        ˇconst B: u32 = 42;
19603        const C: u32 = 42;
19604
19605
19606        fn main() {
19607            println!("hello");
19608
19609            println!("world");
19610        }
19611      "#
19612        .unindent(),
19613    );
19614
19615    cx.update_editor(|editor, window, cx| {
19616        editor.delete_line(&DeleteLine, window, cx);
19617    });
19618    executor.run_until_parked();
19619    cx.assert_state_with_diff(
19620        r#"
19621        use some::mod1;
19622        use some::mod2;
19623
19624      - const A: u32 = 42;
19625      - const B: u32 = 42;
19626        ˇconst C: u32 = 42;
19627
19628
19629        fn main() {
19630            println!("hello");
19631
19632            println!("world");
19633        }
19634      "#
19635        .unindent(),
19636    );
19637
19638    cx.update_editor(|editor, window, cx| {
19639        editor.delete_line(&DeleteLine, window, cx);
19640    });
19641    executor.run_until_parked();
19642    cx.assert_state_with_diff(
19643        r#"
19644        use some::mod1;
19645        use some::mod2;
19646
19647      - const A: u32 = 42;
19648      - const B: u32 = 42;
19649      - const C: u32 = 42;
19650        ˇ
19651
19652        fn main() {
19653            println!("hello");
19654
19655            println!("world");
19656        }
19657      "#
19658        .unindent(),
19659    );
19660
19661    cx.update_editor(|editor, window, cx| {
19662        editor.handle_input("replacement", window, cx);
19663    });
19664    executor.run_until_parked();
19665    cx.assert_state_with_diff(
19666        r#"
19667        use some::mod1;
19668        use some::mod2;
19669
19670      - const A: u32 = 42;
19671      - const B: u32 = 42;
19672      - const C: u32 = 42;
19673      -
19674      + replacementˇ
19675
19676        fn main() {
19677            println!("hello");
19678
19679            println!("world");
19680        }
19681      "#
19682        .unindent(),
19683    );
19684}
19685
19686#[gpui::test]
19687async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19688    init_test(cx, |_| {});
19689
19690    let mut cx = EditorTestContext::new(cx).await;
19691
19692    let base_text = r#"
19693        one
19694        two
19695        three
19696        four
19697        five
19698    "#
19699    .unindent();
19700    executor.run_until_parked();
19701    cx.set_state(
19702        &r#"
19703        one
19704        two
19705        fˇour
19706        five
19707        "#
19708        .unindent(),
19709    );
19710
19711    cx.set_head_text(&base_text);
19712    executor.run_until_parked();
19713
19714    cx.update_editor(|editor, window, cx| {
19715        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19716    });
19717    executor.run_until_parked();
19718
19719    cx.assert_state_with_diff(
19720        r#"
19721          one
19722          two
19723        - three
19724          fˇour
19725          five
19726        "#
19727        .unindent(),
19728    );
19729
19730    cx.update_editor(|editor, window, cx| {
19731        editor.backspace(&Backspace, window, cx);
19732        editor.backspace(&Backspace, window, cx);
19733    });
19734    executor.run_until_parked();
19735    cx.assert_state_with_diff(
19736        r#"
19737          one
19738          two
19739        - threeˇ
19740        - four
19741        + our
19742          five
19743        "#
19744        .unindent(),
19745    );
19746}
19747
19748#[gpui::test]
19749async fn test_edit_after_expanded_modification_hunk(
19750    executor: BackgroundExecutor,
19751    cx: &mut TestAppContext,
19752) {
19753    init_test(cx, |_| {});
19754
19755    let mut cx = EditorTestContext::new(cx).await;
19756
19757    let diff_base = r#"
19758        use some::mod1;
19759        use some::mod2;
19760
19761        const A: u32 = 42;
19762        const B: u32 = 42;
19763        const C: u32 = 42;
19764        const D: u32 = 42;
19765
19766
19767        fn main() {
19768            println!("hello");
19769
19770            println!("world");
19771        }"#
19772    .unindent();
19773
19774    cx.set_state(
19775        &r#"
19776        use some::mod1;
19777        use some::mod2;
19778
19779        const A: u32 = 42;
19780        const B: u32 = 42;
19781        const C: u32 = 43ˇ
19782        const D: u32 = 42;
19783
19784
19785        fn main() {
19786            println!("hello");
19787
19788            println!("world");
19789        }"#
19790        .unindent(),
19791    );
19792
19793    cx.set_head_text(&diff_base);
19794    executor.run_until_parked();
19795    cx.update_editor(|editor, window, cx| {
19796        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19797    });
19798    executor.run_until_parked();
19799
19800    cx.assert_state_with_diff(
19801        r#"
19802        use some::mod1;
19803        use some::mod2;
19804
19805        const A: u32 = 42;
19806        const B: u32 = 42;
19807      - const C: u32 = 42;
19808      + const C: u32 = 43ˇ
19809        const D: u32 = 42;
19810
19811
19812        fn main() {
19813            println!("hello");
19814
19815            println!("world");
19816        }"#
19817        .unindent(),
19818    );
19819
19820    cx.update_editor(|editor, window, cx| {
19821        editor.handle_input("\nnew_line\n", window, cx);
19822    });
19823    executor.run_until_parked();
19824
19825    cx.assert_state_with_diff(
19826        r#"
19827        use some::mod1;
19828        use some::mod2;
19829
19830        const A: u32 = 42;
19831        const B: u32 = 42;
19832      - const C: u32 = 42;
19833      + const C: u32 = 43
19834      + new_line
19835      + ˇ
19836        const D: u32 = 42;
19837
19838
19839        fn main() {
19840            println!("hello");
19841
19842            println!("world");
19843        }"#
19844        .unindent(),
19845    );
19846}
19847
19848#[gpui::test]
19849async fn test_stage_and_unstage_added_file_hunk(
19850    executor: BackgroundExecutor,
19851    cx: &mut TestAppContext,
19852) {
19853    init_test(cx, |_| {});
19854
19855    let mut cx = EditorTestContext::new(cx).await;
19856    cx.update_editor(|editor, _, cx| {
19857        editor.set_expand_all_diff_hunks(cx);
19858    });
19859
19860    let working_copy = r#"
19861            ˇfn main() {
19862                println!("hello, world!");
19863            }
19864        "#
19865    .unindent();
19866
19867    cx.set_state(&working_copy);
19868    executor.run_until_parked();
19869
19870    cx.assert_state_with_diff(
19871        r#"
19872            + ˇfn main() {
19873            +     println!("hello, world!");
19874            + }
19875        "#
19876        .unindent(),
19877    );
19878    cx.assert_index_text(None);
19879
19880    cx.update_editor(|editor, window, cx| {
19881        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19882    });
19883    executor.run_until_parked();
19884    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
19885    cx.assert_state_with_diff(
19886        r#"
19887            + ˇfn main() {
19888            +     println!("hello, world!");
19889            + }
19890        "#
19891        .unindent(),
19892    );
19893
19894    cx.update_editor(|editor, window, cx| {
19895        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19896    });
19897    executor.run_until_parked();
19898    cx.assert_index_text(None);
19899}
19900
19901async fn setup_indent_guides_editor(
19902    text: &str,
19903    cx: &mut TestAppContext,
19904) -> (BufferId, EditorTestContext) {
19905    init_test(cx, |_| {});
19906
19907    let mut cx = EditorTestContext::new(cx).await;
19908
19909    let buffer_id = cx.update_editor(|editor, window, cx| {
19910        editor.set_text(text, window, cx);
19911        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
19912
19913        buffer_ids[0]
19914    });
19915
19916    (buffer_id, cx)
19917}
19918
19919fn assert_indent_guides(
19920    range: Range<u32>,
19921    expected: Vec<IndentGuide>,
19922    active_indices: Option<Vec<usize>>,
19923    cx: &mut EditorTestContext,
19924) {
19925    let indent_guides = cx.update_editor(|editor, window, cx| {
19926        let snapshot = editor.snapshot(window, cx).display_snapshot;
19927        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
19928            editor,
19929            MultiBufferRow(range.start)..MultiBufferRow(range.end),
19930            true,
19931            &snapshot,
19932            cx,
19933        );
19934
19935        indent_guides.sort_by(|a, b| {
19936            a.depth.cmp(&b.depth).then(
19937                a.start_row
19938                    .cmp(&b.start_row)
19939                    .then(a.end_row.cmp(&b.end_row)),
19940            )
19941        });
19942        indent_guides
19943    });
19944
19945    if let Some(expected) = active_indices {
19946        let active_indices = cx.update_editor(|editor, window, cx| {
19947            let snapshot = editor.snapshot(window, cx).display_snapshot;
19948            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
19949        });
19950
19951        assert_eq!(
19952            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
19953            expected,
19954            "Active indent guide indices do not match"
19955        );
19956    }
19957
19958    assert_eq!(indent_guides, expected, "Indent guides do not match");
19959}
19960
19961fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
19962    IndentGuide {
19963        buffer_id,
19964        start_row: MultiBufferRow(start_row),
19965        end_row: MultiBufferRow(end_row),
19966        depth,
19967        tab_size: 4,
19968        settings: IndentGuideSettings {
19969            enabled: true,
19970            line_width: 1,
19971            active_line_width: 1,
19972            coloring: IndentGuideColoring::default(),
19973            background_coloring: IndentGuideBackgroundColoring::default(),
19974        },
19975    }
19976}
19977
19978#[gpui::test]
19979async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
19980    let (buffer_id, mut cx) = setup_indent_guides_editor(
19981        &"
19982        fn main() {
19983            let a = 1;
19984        }"
19985        .unindent(),
19986        cx,
19987    )
19988    .await;
19989
19990    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
19991}
19992
19993#[gpui::test]
19994async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
19995    let (buffer_id, mut cx) = setup_indent_guides_editor(
19996        &"
19997        fn main() {
19998            let a = 1;
19999            let b = 2;
20000        }"
20001        .unindent(),
20002        cx,
20003    )
20004    .await;
20005
20006    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20007}
20008
20009#[gpui::test]
20010async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20011    let (buffer_id, mut cx) = setup_indent_guides_editor(
20012        &"
20013        fn main() {
20014            let a = 1;
20015            if a == 3 {
20016                let b = 2;
20017            } else {
20018                let c = 3;
20019            }
20020        }"
20021        .unindent(),
20022        cx,
20023    )
20024    .await;
20025
20026    assert_indent_guides(
20027        0..8,
20028        vec![
20029            indent_guide(buffer_id, 1, 6, 0),
20030            indent_guide(buffer_id, 3, 3, 1),
20031            indent_guide(buffer_id, 5, 5, 1),
20032        ],
20033        None,
20034        &mut cx,
20035    );
20036}
20037
20038#[gpui::test]
20039async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20040    let (buffer_id, mut cx) = setup_indent_guides_editor(
20041        &"
20042        fn main() {
20043            let a = 1;
20044                let b = 2;
20045            let c = 3;
20046        }"
20047        .unindent(),
20048        cx,
20049    )
20050    .await;
20051
20052    assert_indent_guides(
20053        0..5,
20054        vec![
20055            indent_guide(buffer_id, 1, 3, 0),
20056            indent_guide(buffer_id, 2, 2, 1),
20057        ],
20058        None,
20059        &mut cx,
20060    );
20061}
20062
20063#[gpui::test]
20064async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20065    let (buffer_id, mut cx) = setup_indent_guides_editor(
20066        &"
20067        fn main() {
20068            let a = 1;
20069
20070            let c = 3;
20071        }"
20072        .unindent(),
20073        cx,
20074    )
20075    .await;
20076
20077    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20078}
20079
20080#[gpui::test]
20081async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20082    let (buffer_id, mut cx) = setup_indent_guides_editor(
20083        &"
20084        fn main() {
20085            let a = 1;
20086
20087            let c = 3;
20088
20089            if a == 3 {
20090                let b = 2;
20091            } else {
20092                let c = 3;
20093            }
20094        }"
20095        .unindent(),
20096        cx,
20097    )
20098    .await;
20099
20100    assert_indent_guides(
20101        0..11,
20102        vec![
20103            indent_guide(buffer_id, 1, 9, 0),
20104            indent_guide(buffer_id, 6, 6, 1),
20105            indent_guide(buffer_id, 8, 8, 1),
20106        ],
20107        None,
20108        &mut cx,
20109    );
20110}
20111
20112#[gpui::test]
20113async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20114    let (buffer_id, mut cx) = setup_indent_guides_editor(
20115        &"
20116        fn main() {
20117            let a = 1;
20118
20119            let c = 3;
20120
20121            if a == 3 {
20122                let b = 2;
20123            } else {
20124                let c = 3;
20125            }
20126        }"
20127        .unindent(),
20128        cx,
20129    )
20130    .await;
20131
20132    assert_indent_guides(
20133        1..11,
20134        vec![
20135            indent_guide(buffer_id, 1, 9, 0),
20136            indent_guide(buffer_id, 6, 6, 1),
20137            indent_guide(buffer_id, 8, 8, 1),
20138        ],
20139        None,
20140        &mut cx,
20141    );
20142}
20143
20144#[gpui::test]
20145async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20146    let (buffer_id, mut cx) = setup_indent_guides_editor(
20147        &"
20148        fn main() {
20149            let a = 1;
20150
20151            let c = 3;
20152
20153            if a == 3 {
20154                let b = 2;
20155            } else {
20156                let c = 3;
20157            }
20158        }"
20159        .unindent(),
20160        cx,
20161    )
20162    .await;
20163
20164    assert_indent_guides(
20165        1..10,
20166        vec![
20167            indent_guide(buffer_id, 1, 9, 0),
20168            indent_guide(buffer_id, 6, 6, 1),
20169            indent_guide(buffer_id, 8, 8, 1),
20170        ],
20171        None,
20172        &mut cx,
20173    );
20174}
20175
20176#[gpui::test]
20177async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20178    let (buffer_id, mut cx) = setup_indent_guides_editor(
20179        &"
20180        fn main() {
20181            if a {
20182                b(
20183                    c,
20184                    d,
20185                )
20186            } else {
20187                e(
20188                    f
20189                )
20190            }
20191        }"
20192        .unindent(),
20193        cx,
20194    )
20195    .await;
20196
20197    assert_indent_guides(
20198        0..11,
20199        vec![
20200            indent_guide(buffer_id, 1, 10, 0),
20201            indent_guide(buffer_id, 2, 5, 1),
20202            indent_guide(buffer_id, 7, 9, 1),
20203            indent_guide(buffer_id, 3, 4, 2),
20204            indent_guide(buffer_id, 8, 8, 2),
20205        ],
20206        None,
20207        &mut cx,
20208    );
20209
20210    cx.update_editor(|editor, window, cx| {
20211        editor.fold_at(MultiBufferRow(2), window, cx);
20212        assert_eq!(
20213            editor.display_text(cx),
20214            "
20215            fn main() {
20216                if a {
20217                    b(⋯
20218                    )
20219                } else {
20220                    e(
20221                        f
20222                    )
20223                }
20224            }"
20225            .unindent()
20226        );
20227    });
20228
20229    assert_indent_guides(
20230        0..11,
20231        vec![
20232            indent_guide(buffer_id, 1, 10, 0),
20233            indent_guide(buffer_id, 2, 5, 1),
20234            indent_guide(buffer_id, 7, 9, 1),
20235            indent_guide(buffer_id, 8, 8, 2),
20236        ],
20237        None,
20238        &mut cx,
20239    );
20240}
20241
20242#[gpui::test]
20243async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20244    let (buffer_id, mut cx) = setup_indent_guides_editor(
20245        &"
20246        block1
20247            block2
20248                block3
20249                    block4
20250            block2
20251        block1
20252        block1"
20253            .unindent(),
20254        cx,
20255    )
20256    .await;
20257
20258    assert_indent_guides(
20259        1..10,
20260        vec![
20261            indent_guide(buffer_id, 1, 4, 0),
20262            indent_guide(buffer_id, 2, 3, 1),
20263            indent_guide(buffer_id, 3, 3, 2),
20264        ],
20265        None,
20266        &mut cx,
20267    );
20268}
20269
20270#[gpui::test]
20271async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20272    let (buffer_id, mut cx) = setup_indent_guides_editor(
20273        &"
20274        block1
20275            block2
20276                block3
20277
20278        block1
20279        block1"
20280            .unindent(),
20281        cx,
20282    )
20283    .await;
20284
20285    assert_indent_guides(
20286        0..6,
20287        vec![
20288            indent_guide(buffer_id, 1, 2, 0),
20289            indent_guide(buffer_id, 2, 2, 1),
20290        ],
20291        None,
20292        &mut cx,
20293    );
20294}
20295
20296#[gpui::test]
20297async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20298    let (buffer_id, mut cx) = setup_indent_guides_editor(
20299        &"
20300        function component() {
20301        \treturn (
20302        \t\t\t
20303        \t\t<div>
20304        \t\t\t<abc></abc>
20305        \t\t</div>
20306        \t)
20307        }"
20308        .unindent(),
20309        cx,
20310    )
20311    .await;
20312
20313    assert_indent_guides(
20314        0..8,
20315        vec![
20316            indent_guide(buffer_id, 1, 6, 0),
20317            indent_guide(buffer_id, 2, 5, 1),
20318            indent_guide(buffer_id, 4, 4, 2),
20319        ],
20320        None,
20321        &mut cx,
20322    );
20323}
20324
20325#[gpui::test]
20326async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20327    let (buffer_id, mut cx) = setup_indent_guides_editor(
20328        &"
20329        function component() {
20330        \treturn (
20331        \t
20332        \t\t<div>
20333        \t\t\t<abc></abc>
20334        \t\t</div>
20335        \t)
20336        }"
20337        .unindent(),
20338        cx,
20339    )
20340    .await;
20341
20342    assert_indent_guides(
20343        0..8,
20344        vec![
20345            indent_guide(buffer_id, 1, 6, 0),
20346            indent_guide(buffer_id, 2, 5, 1),
20347            indent_guide(buffer_id, 4, 4, 2),
20348        ],
20349        None,
20350        &mut cx,
20351    );
20352}
20353
20354#[gpui::test]
20355async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20356    let (buffer_id, mut cx) = setup_indent_guides_editor(
20357        &"
20358        block1
20359
20360
20361
20362            block2
20363        "
20364        .unindent(),
20365        cx,
20366    )
20367    .await;
20368
20369    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20370}
20371
20372#[gpui::test]
20373async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20374    let (buffer_id, mut cx) = setup_indent_guides_editor(
20375        &"
20376        def a:
20377        \tb = 3
20378        \tif True:
20379        \t\tc = 4
20380        \t\td = 5
20381        \tprint(b)
20382        "
20383        .unindent(),
20384        cx,
20385    )
20386    .await;
20387
20388    assert_indent_guides(
20389        0..6,
20390        vec![
20391            indent_guide(buffer_id, 1, 5, 0),
20392            indent_guide(buffer_id, 3, 4, 1),
20393        ],
20394        None,
20395        &mut cx,
20396    );
20397}
20398
20399#[gpui::test]
20400async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20401    let (buffer_id, mut cx) = setup_indent_guides_editor(
20402        &"
20403    fn main() {
20404        let a = 1;
20405    }"
20406        .unindent(),
20407        cx,
20408    )
20409    .await;
20410
20411    cx.update_editor(|editor, window, cx| {
20412        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20413            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20414        });
20415    });
20416
20417    assert_indent_guides(
20418        0..3,
20419        vec![indent_guide(buffer_id, 1, 1, 0)],
20420        Some(vec![0]),
20421        &mut cx,
20422    );
20423}
20424
20425#[gpui::test]
20426async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20427    let (buffer_id, mut cx) = setup_indent_guides_editor(
20428        &"
20429    fn main() {
20430        if 1 == 2 {
20431            let a = 1;
20432        }
20433    }"
20434        .unindent(),
20435        cx,
20436    )
20437    .await;
20438
20439    cx.update_editor(|editor, window, cx| {
20440        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20441            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20442        });
20443    });
20444
20445    assert_indent_guides(
20446        0..4,
20447        vec![
20448            indent_guide(buffer_id, 1, 3, 0),
20449            indent_guide(buffer_id, 2, 2, 1),
20450        ],
20451        Some(vec![1]),
20452        &mut cx,
20453    );
20454
20455    cx.update_editor(|editor, window, cx| {
20456        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20457            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20458        });
20459    });
20460
20461    assert_indent_guides(
20462        0..4,
20463        vec![
20464            indent_guide(buffer_id, 1, 3, 0),
20465            indent_guide(buffer_id, 2, 2, 1),
20466        ],
20467        Some(vec![1]),
20468        &mut cx,
20469    );
20470
20471    cx.update_editor(|editor, window, cx| {
20472        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20473            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20474        });
20475    });
20476
20477    assert_indent_guides(
20478        0..4,
20479        vec![
20480            indent_guide(buffer_id, 1, 3, 0),
20481            indent_guide(buffer_id, 2, 2, 1),
20482        ],
20483        Some(vec![0]),
20484        &mut cx,
20485    );
20486}
20487
20488#[gpui::test]
20489async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20490    let (buffer_id, mut cx) = setup_indent_guides_editor(
20491        &"
20492    fn main() {
20493        let a = 1;
20494
20495        let b = 2;
20496    }"
20497        .unindent(),
20498        cx,
20499    )
20500    .await;
20501
20502    cx.update_editor(|editor, window, cx| {
20503        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20504            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20505        });
20506    });
20507
20508    assert_indent_guides(
20509        0..5,
20510        vec![indent_guide(buffer_id, 1, 3, 0)],
20511        Some(vec![0]),
20512        &mut cx,
20513    );
20514}
20515
20516#[gpui::test]
20517async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20518    let (buffer_id, mut cx) = setup_indent_guides_editor(
20519        &"
20520    def m:
20521        a = 1
20522        pass"
20523            .unindent(),
20524        cx,
20525    )
20526    .await;
20527
20528    cx.update_editor(|editor, window, cx| {
20529        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20530            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20531        });
20532    });
20533
20534    assert_indent_guides(
20535        0..3,
20536        vec![indent_guide(buffer_id, 1, 2, 0)],
20537        Some(vec![0]),
20538        &mut cx,
20539    );
20540}
20541
20542#[gpui::test]
20543async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20544    init_test(cx, |_| {});
20545    let mut cx = EditorTestContext::new(cx).await;
20546    let text = indoc! {
20547        "
20548        impl A {
20549            fn b() {
20550                0;
20551                3;
20552                5;
20553                6;
20554                7;
20555            }
20556        }
20557        "
20558    };
20559    let base_text = indoc! {
20560        "
20561        impl A {
20562            fn b() {
20563                0;
20564                1;
20565                2;
20566                3;
20567                4;
20568            }
20569            fn c() {
20570                5;
20571                6;
20572                7;
20573            }
20574        }
20575        "
20576    };
20577
20578    cx.update_editor(|editor, window, cx| {
20579        editor.set_text(text, window, cx);
20580
20581        editor.buffer().update(cx, |multibuffer, cx| {
20582            let buffer = multibuffer.as_singleton().unwrap();
20583            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20584
20585            multibuffer.set_all_diff_hunks_expanded(cx);
20586            multibuffer.add_diff(diff, cx);
20587
20588            buffer.read(cx).remote_id()
20589        })
20590    });
20591    cx.run_until_parked();
20592
20593    cx.assert_state_with_diff(
20594        indoc! { "
20595          impl A {
20596              fn b() {
20597                  0;
20598        -         1;
20599        -         2;
20600                  3;
20601        -         4;
20602        -     }
20603        -     fn c() {
20604                  5;
20605                  6;
20606                  7;
20607              }
20608          }
20609          ˇ"
20610        }
20611        .to_string(),
20612    );
20613
20614    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20615        editor
20616            .snapshot(window, cx)
20617            .buffer_snapshot
20618            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20619            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20620            .collect::<Vec<_>>()
20621    });
20622    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20623    assert_eq!(
20624        actual_guides,
20625        vec![
20626            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20627            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20628            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20629        ]
20630    );
20631}
20632
20633#[gpui::test]
20634async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20635    init_test(cx, |_| {});
20636    let mut cx = EditorTestContext::new(cx).await;
20637
20638    let diff_base = r#"
20639        a
20640        b
20641        c
20642        "#
20643    .unindent();
20644
20645    cx.set_state(
20646        &r#"
20647        ˇA
20648        b
20649        C
20650        "#
20651        .unindent(),
20652    );
20653    cx.set_head_text(&diff_base);
20654    cx.update_editor(|editor, window, cx| {
20655        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20656    });
20657    executor.run_until_parked();
20658
20659    let both_hunks_expanded = r#"
20660        - a
20661        + ˇA
20662          b
20663        - c
20664        + C
20665        "#
20666    .unindent();
20667
20668    cx.assert_state_with_diff(both_hunks_expanded.clone());
20669
20670    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20671        let snapshot = editor.snapshot(window, cx);
20672        let hunks = editor
20673            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20674            .collect::<Vec<_>>();
20675        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20676        let buffer_id = hunks[0].buffer_id;
20677        hunks
20678            .into_iter()
20679            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20680            .collect::<Vec<_>>()
20681    });
20682    assert_eq!(hunk_ranges.len(), 2);
20683
20684    cx.update_editor(|editor, _, cx| {
20685        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20686    });
20687    executor.run_until_parked();
20688
20689    let second_hunk_expanded = r#"
20690          ˇA
20691          b
20692        - c
20693        + C
20694        "#
20695    .unindent();
20696
20697    cx.assert_state_with_diff(second_hunk_expanded);
20698
20699    cx.update_editor(|editor, _, cx| {
20700        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20701    });
20702    executor.run_until_parked();
20703
20704    cx.assert_state_with_diff(both_hunks_expanded.clone());
20705
20706    cx.update_editor(|editor, _, cx| {
20707        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20708    });
20709    executor.run_until_parked();
20710
20711    let first_hunk_expanded = r#"
20712        - a
20713        + ˇA
20714          b
20715          C
20716        "#
20717    .unindent();
20718
20719    cx.assert_state_with_diff(first_hunk_expanded);
20720
20721    cx.update_editor(|editor, _, cx| {
20722        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20723    });
20724    executor.run_until_parked();
20725
20726    cx.assert_state_with_diff(both_hunks_expanded);
20727
20728    cx.set_state(
20729        &r#"
20730        ˇA
20731        b
20732        "#
20733        .unindent(),
20734    );
20735    cx.run_until_parked();
20736
20737    // TODO this cursor position seems bad
20738    cx.assert_state_with_diff(
20739        r#"
20740        - ˇa
20741        + A
20742          b
20743        "#
20744        .unindent(),
20745    );
20746
20747    cx.update_editor(|editor, window, cx| {
20748        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20749    });
20750
20751    cx.assert_state_with_diff(
20752        r#"
20753            - ˇa
20754            + A
20755              b
20756            - c
20757            "#
20758        .unindent(),
20759    );
20760
20761    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20762        let snapshot = editor.snapshot(window, cx);
20763        let hunks = editor
20764            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20765            .collect::<Vec<_>>();
20766        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20767        let buffer_id = hunks[0].buffer_id;
20768        hunks
20769            .into_iter()
20770            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20771            .collect::<Vec<_>>()
20772    });
20773    assert_eq!(hunk_ranges.len(), 2);
20774
20775    cx.update_editor(|editor, _, cx| {
20776        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20777    });
20778    executor.run_until_parked();
20779
20780    cx.assert_state_with_diff(
20781        r#"
20782        - ˇa
20783        + A
20784          b
20785        "#
20786        .unindent(),
20787    );
20788}
20789
20790#[gpui::test]
20791async fn test_toggle_deletion_hunk_at_start_of_file(
20792    executor: BackgroundExecutor,
20793    cx: &mut TestAppContext,
20794) {
20795    init_test(cx, |_| {});
20796    let mut cx = EditorTestContext::new(cx).await;
20797
20798    let diff_base = r#"
20799        a
20800        b
20801        c
20802        "#
20803    .unindent();
20804
20805    cx.set_state(
20806        &r#"
20807        ˇb
20808        c
20809        "#
20810        .unindent(),
20811    );
20812    cx.set_head_text(&diff_base);
20813    cx.update_editor(|editor, window, cx| {
20814        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20815    });
20816    executor.run_until_parked();
20817
20818    let hunk_expanded = r#"
20819        - a
20820          ˇb
20821          c
20822        "#
20823    .unindent();
20824
20825    cx.assert_state_with_diff(hunk_expanded.clone());
20826
20827    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20828        let snapshot = editor.snapshot(window, cx);
20829        let hunks = editor
20830            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20831            .collect::<Vec<_>>();
20832        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20833        let buffer_id = hunks[0].buffer_id;
20834        hunks
20835            .into_iter()
20836            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20837            .collect::<Vec<_>>()
20838    });
20839    assert_eq!(hunk_ranges.len(), 1);
20840
20841    cx.update_editor(|editor, _, cx| {
20842        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20843    });
20844    executor.run_until_parked();
20845
20846    let hunk_collapsed = r#"
20847          ˇb
20848          c
20849        "#
20850    .unindent();
20851
20852    cx.assert_state_with_diff(hunk_collapsed);
20853
20854    cx.update_editor(|editor, _, cx| {
20855        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20856    });
20857    executor.run_until_parked();
20858
20859    cx.assert_state_with_diff(hunk_expanded);
20860}
20861
20862#[gpui::test]
20863async fn test_display_diff_hunks(cx: &mut TestAppContext) {
20864    init_test(cx, |_| {});
20865
20866    let fs = FakeFs::new(cx.executor());
20867    fs.insert_tree(
20868        path!("/test"),
20869        json!({
20870            ".git": {},
20871            "file-1": "ONE\n",
20872            "file-2": "TWO\n",
20873            "file-3": "THREE\n",
20874        }),
20875    )
20876    .await;
20877
20878    fs.set_head_for_repo(
20879        path!("/test/.git").as_ref(),
20880        &[
20881            ("file-1".into(), "one\n".into()),
20882            ("file-2".into(), "two\n".into()),
20883            ("file-3".into(), "three\n".into()),
20884        ],
20885        "deadbeef",
20886    );
20887
20888    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
20889    let mut buffers = vec![];
20890    for i in 1..=3 {
20891        let buffer = project
20892            .update(cx, |project, cx| {
20893                let path = format!(path!("/test/file-{}"), i);
20894                project.open_local_buffer(path, cx)
20895            })
20896            .await
20897            .unwrap();
20898        buffers.push(buffer);
20899    }
20900
20901    let multibuffer = cx.new(|cx| {
20902        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
20903        multibuffer.set_all_diff_hunks_expanded(cx);
20904        for buffer in &buffers {
20905            let snapshot = buffer.read(cx).snapshot();
20906            multibuffer.set_excerpts_for_path(
20907                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
20908                buffer.clone(),
20909                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
20910                2,
20911                cx,
20912            );
20913        }
20914        multibuffer
20915    });
20916
20917    let editor = cx.add_window(|window, cx| {
20918        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
20919    });
20920    cx.run_until_parked();
20921
20922    let snapshot = editor
20923        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20924        .unwrap();
20925    let hunks = snapshot
20926        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
20927        .map(|hunk| match hunk {
20928            DisplayDiffHunk::Unfolded {
20929                display_row_range, ..
20930            } => display_row_range,
20931            DisplayDiffHunk::Folded { .. } => unreachable!(),
20932        })
20933        .collect::<Vec<_>>();
20934    assert_eq!(
20935        hunks,
20936        [
20937            DisplayRow(2)..DisplayRow(4),
20938            DisplayRow(7)..DisplayRow(9),
20939            DisplayRow(12)..DisplayRow(14),
20940        ]
20941    );
20942}
20943
20944#[gpui::test]
20945async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
20946    init_test(cx, |_| {});
20947
20948    let mut cx = EditorTestContext::new(cx).await;
20949    cx.set_head_text(indoc! { "
20950        one
20951        two
20952        three
20953        four
20954        five
20955        "
20956    });
20957    cx.set_index_text(indoc! { "
20958        one
20959        two
20960        three
20961        four
20962        five
20963        "
20964    });
20965    cx.set_state(indoc! {"
20966        one
20967        TWO
20968        ˇTHREE
20969        FOUR
20970        five
20971    "});
20972    cx.run_until_parked();
20973    cx.update_editor(|editor, window, cx| {
20974        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20975    });
20976    cx.run_until_parked();
20977    cx.assert_index_text(Some(indoc! {"
20978        one
20979        TWO
20980        THREE
20981        FOUR
20982        five
20983    "}));
20984    cx.set_state(indoc! { "
20985        one
20986        TWO
20987        ˇTHREE-HUNDRED
20988        FOUR
20989        five
20990    "});
20991    cx.run_until_parked();
20992    cx.update_editor(|editor, window, cx| {
20993        let snapshot = editor.snapshot(window, cx);
20994        let hunks = editor
20995            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20996            .collect::<Vec<_>>();
20997        assert_eq!(hunks.len(), 1);
20998        assert_eq!(
20999            hunks[0].status(),
21000            DiffHunkStatus {
21001                kind: DiffHunkStatusKind::Modified,
21002                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21003            }
21004        );
21005
21006        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21007    });
21008    cx.run_until_parked();
21009    cx.assert_index_text(Some(indoc! {"
21010        one
21011        TWO
21012        THREE-HUNDRED
21013        FOUR
21014        five
21015    "}));
21016}
21017
21018#[gpui::test]
21019fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21020    init_test(cx, |_| {});
21021
21022    let editor = cx.add_window(|window, cx| {
21023        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21024        build_editor(buffer, window, cx)
21025    });
21026
21027    let render_args = Arc::new(Mutex::new(None));
21028    let snapshot = editor
21029        .update(cx, |editor, window, cx| {
21030            let snapshot = editor.buffer().read(cx).snapshot(cx);
21031            let range =
21032                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21033
21034            struct RenderArgs {
21035                row: MultiBufferRow,
21036                folded: bool,
21037                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21038            }
21039
21040            let crease = Crease::inline(
21041                range,
21042                FoldPlaceholder::test(),
21043                {
21044                    let toggle_callback = render_args.clone();
21045                    move |row, folded, callback, _window, _cx| {
21046                        *toggle_callback.lock() = Some(RenderArgs {
21047                            row,
21048                            folded,
21049                            callback,
21050                        });
21051                        div()
21052                    }
21053                },
21054                |_row, _folded, _window, _cx| div(),
21055            );
21056
21057            editor.insert_creases(Some(crease), cx);
21058            let snapshot = editor.snapshot(window, cx);
21059            let _div =
21060                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21061            snapshot
21062        })
21063        .unwrap();
21064
21065    let render_args = render_args.lock().take().unwrap();
21066    assert_eq!(render_args.row, MultiBufferRow(1));
21067    assert!(!render_args.folded);
21068    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21069
21070    cx.update_window(*editor, |_, window, cx| {
21071        (render_args.callback)(true, window, cx)
21072    })
21073    .unwrap();
21074    let snapshot = editor
21075        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21076        .unwrap();
21077    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21078
21079    cx.update_window(*editor, |_, window, cx| {
21080        (render_args.callback)(false, window, cx)
21081    })
21082    .unwrap();
21083    let snapshot = editor
21084        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21085        .unwrap();
21086    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21087}
21088
21089#[gpui::test]
21090async fn test_input_text(cx: &mut TestAppContext) {
21091    init_test(cx, |_| {});
21092    let mut cx = EditorTestContext::new(cx).await;
21093
21094    cx.set_state(
21095        &r#"ˇone
21096        two
21097
21098        three
21099        fourˇ
21100        five
21101
21102        siˇx"#
21103            .unindent(),
21104    );
21105
21106    cx.dispatch_action(HandleInput(String::new()));
21107    cx.assert_editor_state(
21108        &r#"ˇone
21109        two
21110
21111        three
21112        fourˇ
21113        five
21114
21115        siˇx"#
21116            .unindent(),
21117    );
21118
21119    cx.dispatch_action(HandleInput("AAAA".to_string()));
21120    cx.assert_editor_state(
21121        &r#"AAAAˇone
21122        two
21123
21124        three
21125        fourAAAAˇ
21126        five
21127
21128        siAAAAˇx"#
21129            .unindent(),
21130    );
21131}
21132
21133#[gpui::test]
21134async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21135    init_test(cx, |_| {});
21136
21137    let mut cx = EditorTestContext::new(cx).await;
21138    cx.set_state(
21139        r#"let foo = 1;
21140let foo = 2;
21141let foo = 3;
21142let fooˇ = 4;
21143let foo = 5;
21144let foo = 6;
21145let foo = 7;
21146let foo = 8;
21147let foo = 9;
21148let foo = 10;
21149let foo = 11;
21150let foo = 12;
21151let foo = 13;
21152let foo = 14;
21153let foo = 15;"#,
21154    );
21155
21156    cx.update_editor(|e, window, cx| {
21157        assert_eq!(
21158            e.next_scroll_position,
21159            NextScrollCursorCenterTopBottom::Center,
21160            "Default next scroll direction is center",
21161        );
21162
21163        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21164        assert_eq!(
21165            e.next_scroll_position,
21166            NextScrollCursorCenterTopBottom::Top,
21167            "After center, next scroll direction should be top",
21168        );
21169
21170        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21171        assert_eq!(
21172            e.next_scroll_position,
21173            NextScrollCursorCenterTopBottom::Bottom,
21174            "After top, next scroll direction should be bottom",
21175        );
21176
21177        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21178        assert_eq!(
21179            e.next_scroll_position,
21180            NextScrollCursorCenterTopBottom::Center,
21181            "After bottom, scrolling should start over",
21182        );
21183
21184        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21185        assert_eq!(
21186            e.next_scroll_position,
21187            NextScrollCursorCenterTopBottom::Top,
21188            "Scrolling continues if retriggered fast enough"
21189        );
21190    });
21191
21192    cx.executor()
21193        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21194    cx.executor().run_until_parked();
21195    cx.update_editor(|e, _, _| {
21196        assert_eq!(
21197            e.next_scroll_position,
21198            NextScrollCursorCenterTopBottom::Center,
21199            "If scrolling is not triggered fast enough, it should reset"
21200        );
21201    });
21202}
21203
21204#[gpui::test]
21205async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21206    init_test(cx, |_| {});
21207    let mut cx = EditorLspTestContext::new_rust(
21208        lsp::ServerCapabilities {
21209            definition_provider: Some(lsp::OneOf::Left(true)),
21210            references_provider: Some(lsp::OneOf::Left(true)),
21211            ..lsp::ServerCapabilities::default()
21212        },
21213        cx,
21214    )
21215    .await;
21216
21217    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21218        let go_to_definition = cx
21219            .lsp
21220            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21221                move |params, _| async move {
21222                    if empty_go_to_definition {
21223                        Ok(None)
21224                    } else {
21225                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21226                            uri: params.text_document_position_params.text_document.uri,
21227                            range: lsp::Range::new(
21228                                lsp::Position::new(4, 3),
21229                                lsp::Position::new(4, 6),
21230                            ),
21231                        })))
21232                    }
21233                },
21234            );
21235        let references = cx
21236            .lsp
21237            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21238                Ok(Some(vec![lsp::Location {
21239                    uri: params.text_document_position.text_document.uri,
21240                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21241                }]))
21242            });
21243        (go_to_definition, references)
21244    };
21245
21246    cx.set_state(
21247        &r#"fn one() {
21248            let mut a = ˇtwo();
21249        }
21250
21251        fn two() {}"#
21252            .unindent(),
21253    );
21254    set_up_lsp_handlers(false, &mut cx);
21255    let navigated = cx
21256        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21257        .await
21258        .expect("Failed to navigate to definition");
21259    assert_eq!(
21260        navigated,
21261        Navigated::Yes,
21262        "Should have navigated to definition from the GetDefinition response"
21263    );
21264    cx.assert_editor_state(
21265        &r#"fn one() {
21266            let mut a = two();
21267        }
21268
21269        fn «twoˇ»() {}"#
21270            .unindent(),
21271    );
21272
21273    let editors = cx.update_workspace(|workspace, _, cx| {
21274        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21275    });
21276    cx.update_editor(|_, _, test_editor_cx| {
21277        assert_eq!(
21278            editors.len(),
21279            1,
21280            "Initially, only one, test, editor should be open in the workspace"
21281        );
21282        assert_eq!(
21283            test_editor_cx.entity(),
21284            editors.last().expect("Asserted len is 1").clone()
21285        );
21286    });
21287
21288    set_up_lsp_handlers(true, &mut cx);
21289    let navigated = cx
21290        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21291        .await
21292        .expect("Failed to navigate to lookup references");
21293    assert_eq!(
21294        navigated,
21295        Navigated::Yes,
21296        "Should have navigated to references as a fallback after empty GoToDefinition response"
21297    );
21298    // We should not change the selections in the existing file,
21299    // if opening another milti buffer with the references
21300    cx.assert_editor_state(
21301        &r#"fn one() {
21302            let mut a = two();
21303        }
21304
21305        fn «twoˇ»() {}"#
21306            .unindent(),
21307    );
21308    let editors = cx.update_workspace(|workspace, _, cx| {
21309        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21310    });
21311    cx.update_editor(|_, _, test_editor_cx| {
21312        assert_eq!(
21313            editors.len(),
21314            2,
21315            "After falling back to references search, we open a new editor with the results"
21316        );
21317        let references_fallback_text = editors
21318            .into_iter()
21319            .find(|new_editor| *new_editor != test_editor_cx.entity())
21320            .expect("Should have one non-test editor now")
21321            .read(test_editor_cx)
21322            .text(test_editor_cx);
21323        assert_eq!(
21324            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21325            "Should use the range from the references response and not the GoToDefinition one"
21326        );
21327    });
21328}
21329
21330#[gpui::test]
21331async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21332    init_test(cx, |_| {});
21333    cx.update(|cx| {
21334        let mut editor_settings = EditorSettings::get_global(cx).clone();
21335        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21336        EditorSettings::override_global(editor_settings, cx);
21337    });
21338    let mut cx = EditorLspTestContext::new_rust(
21339        lsp::ServerCapabilities {
21340            definition_provider: Some(lsp::OneOf::Left(true)),
21341            references_provider: Some(lsp::OneOf::Left(true)),
21342            ..lsp::ServerCapabilities::default()
21343        },
21344        cx,
21345    )
21346    .await;
21347    let original_state = r#"fn one() {
21348        let mut a = ˇtwo();
21349    }
21350
21351    fn two() {}"#
21352        .unindent();
21353    cx.set_state(&original_state);
21354
21355    let mut go_to_definition = cx
21356        .lsp
21357        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21358            move |_, _| async move { Ok(None) },
21359        );
21360    let _references = cx
21361        .lsp
21362        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21363            panic!("Should not call for references with no go to definition fallback")
21364        });
21365
21366    let navigated = cx
21367        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21368        .await
21369        .expect("Failed to navigate to lookup references");
21370    go_to_definition
21371        .next()
21372        .await
21373        .expect("Should have called the go_to_definition handler");
21374
21375    assert_eq!(
21376        navigated,
21377        Navigated::No,
21378        "Should have navigated to references as a fallback after empty GoToDefinition response"
21379    );
21380    cx.assert_editor_state(&original_state);
21381    let editors = cx.update_workspace(|workspace, _, cx| {
21382        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21383    });
21384    cx.update_editor(|_, _, _| {
21385        assert_eq!(
21386            editors.len(),
21387            1,
21388            "After unsuccessful fallback, no other editor should have been opened"
21389        );
21390    });
21391}
21392
21393#[gpui::test]
21394async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21395    init_test(cx, |_| {});
21396    let mut cx = EditorLspTestContext::new_rust(
21397        lsp::ServerCapabilities {
21398            references_provider: Some(lsp::OneOf::Left(true)),
21399            ..lsp::ServerCapabilities::default()
21400        },
21401        cx,
21402    )
21403    .await;
21404
21405    cx.set_state(
21406        &r#"
21407        fn one() {
21408            let mut a = two();
21409        }
21410
21411        fn ˇtwo() {}"#
21412            .unindent(),
21413    );
21414    cx.lsp
21415        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21416            Ok(Some(vec![
21417                lsp::Location {
21418                    uri: params.text_document_position.text_document.uri.clone(),
21419                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21420                },
21421                lsp::Location {
21422                    uri: params.text_document_position.text_document.uri,
21423                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21424                },
21425            ]))
21426        });
21427    let navigated = cx
21428        .update_editor(|editor, window, cx| {
21429            editor.find_all_references(&FindAllReferences, window, cx)
21430        })
21431        .unwrap()
21432        .await
21433        .expect("Failed to navigate to references");
21434    assert_eq!(
21435        navigated,
21436        Navigated::Yes,
21437        "Should have navigated to references from the FindAllReferences response"
21438    );
21439    cx.assert_editor_state(
21440        &r#"fn one() {
21441            let mut a = two();
21442        }
21443
21444        fn ˇtwo() {}"#
21445            .unindent(),
21446    );
21447
21448    let editors = cx.update_workspace(|workspace, _, cx| {
21449        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21450    });
21451    cx.update_editor(|_, _, _| {
21452        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21453    });
21454
21455    cx.set_state(
21456        &r#"fn one() {
21457            let mut a = ˇtwo();
21458        }
21459
21460        fn two() {}"#
21461            .unindent(),
21462    );
21463    let navigated = cx
21464        .update_editor(|editor, window, cx| {
21465            editor.find_all_references(&FindAllReferences, window, cx)
21466        })
21467        .unwrap()
21468        .await
21469        .expect("Failed to navigate to references");
21470    assert_eq!(
21471        navigated,
21472        Navigated::Yes,
21473        "Should have navigated to references from the FindAllReferences response"
21474    );
21475    cx.assert_editor_state(
21476        &r#"fn one() {
21477            let mut a = ˇtwo();
21478        }
21479
21480        fn two() {}"#
21481            .unindent(),
21482    );
21483    let editors = cx.update_workspace(|workspace, _, cx| {
21484        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21485    });
21486    cx.update_editor(|_, _, _| {
21487        assert_eq!(
21488            editors.len(),
21489            2,
21490            "should have re-used the previous multibuffer"
21491        );
21492    });
21493
21494    cx.set_state(
21495        &r#"fn one() {
21496            let mut a = ˇtwo();
21497        }
21498        fn three() {}
21499        fn two() {}"#
21500            .unindent(),
21501    );
21502    cx.lsp
21503        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21504            Ok(Some(vec![
21505                lsp::Location {
21506                    uri: params.text_document_position.text_document.uri.clone(),
21507                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21508                },
21509                lsp::Location {
21510                    uri: params.text_document_position.text_document.uri,
21511                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21512                },
21513            ]))
21514        });
21515    let navigated = cx
21516        .update_editor(|editor, window, cx| {
21517            editor.find_all_references(&FindAllReferences, window, cx)
21518        })
21519        .unwrap()
21520        .await
21521        .expect("Failed to navigate to references");
21522    assert_eq!(
21523        navigated,
21524        Navigated::Yes,
21525        "Should have navigated to references from the FindAllReferences response"
21526    );
21527    cx.assert_editor_state(
21528        &r#"fn one() {
21529                let mut a = ˇtwo();
21530            }
21531            fn three() {}
21532            fn two() {}"#
21533            .unindent(),
21534    );
21535    let editors = cx.update_workspace(|workspace, _, cx| {
21536        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21537    });
21538    cx.update_editor(|_, _, _| {
21539        assert_eq!(
21540            editors.len(),
21541            3,
21542            "should have used a new multibuffer as offsets changed"
21543        );
21544    });
21545}
21546#[gpui::test]
21547async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21548    init_test(cx, |_| {});
21549
21550    let language = Arc::new(Language::new(
21551        LanguageConfig::default(),
21552        Some(tree_sitter_rust::LANGUAGE.into()),
21553    ));
21554
21555    let text = r#"
21556        #[cfg(test)]
21557        mod tests() {
21558            #[test]
21559            fn runnable_1() {
21560                let a = 1;
21561            }
21562
21563            #[test]
21564            fn runnable_2() {
21565                let a = 1;
21566                let b = 2;
21567            }
21568        }
21569    "#
21570    .unindent();
21571
21572    let fs = FakeFs::new(cx.executor());
21573    fs.insert_file("/file.rs", Default::default()).await;
21574
21575    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21576    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21577    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21578    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21579    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21580
21581    let editor = cx.new_window_entity(|window, cx| {
21582        Editor::new(
21583            EditorMode::full(),
21584            multi_buffer,
21585            Some(project.clone()),
21586            window,
21587            cx,
21588        )
21589    });
21590
21591    editor.update_in(cx, |editor, window, cx| {
21592        let snapshot = editor.buffer().read(cx).snapshot(cx);
21593        editor.tasks.insert(
21594            (buffer.read(cx).remote_id(), 3),
21595            RunnableTasks {
21596                templates: vec![],
21597                offset: snapshot.anchor_before(43),
21598                column: 0,
21599                extra_variables: HashMap::default(),
21600                context_range: BufferOffset(43)..BufferOffset(85),
21601            },
21602        );
21603        editor.tasks.insert(
21604            (buffer.read(cx).remote_id(), 8),
21605            RunnableTasks {
21606                templates: vec![],
21607                offset: snapshot.anchor_before(86),
21608                column: 0,
21609                extra_variables: HashMap::default(),
21610                context_range: BufferOffset(86)..BufferOffset(191),
21611            },
21612        );
21613
21614        // Test finding task when cursor is inside function body
21615        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21616            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21617        });
21618        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21619        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21620
21621        // Test finding task when cursor is on function name
21622        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21623            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21624        });
21625        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21626        assert_eq!(row, 8, "Should find task when cursor is on function name");
21627    });
21628}
21629
21630#[gpui::test]
21631async fn test_folding_buffers(cx: &mut TestAppContext) {
21632    init_test(cx, |_| {});
21633
21634    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21635    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21636    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21637
21638    let fs = FakeFs::new(cx.executor());
21639    fs.insert_tree(
21640        path!("/a"),
21641        json!({
21642            "first.rs": sample_text_1,
21643            "second.rs": sample_text_2,
21644            "third.rs": sample_text_3,
21645        }),
21646    )
21647    .await;
21648    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21649    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21650    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21651    let worktree = project.update(cx, |project, cx| {
21652        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21653        assert_eq!(worktrees.len(), 1);
21654        worktrees.pop().unwrap()
21655    });
21656    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21657
21658    let buffer_1 = project
21659        .update(cx, |project, cx| {
21660            project.open_buffer((worktree_id, "first.rs"), cx)
21661        })
21662        .await
21663        .unwrap();
21664    let buffer_2 = project
21665        .update(cx, |project, cx| {
21666            project.open_buffer((worktree_id, "second.rs"), cx)
21667        })
21668        .await
21669        .unwrap();
21670    let buffer_3 = project
21671        .update(cx, |project, cx| {
21672            project.open_buffer((worktree_id, "third.rs"), cx)
21673        })
21674        .await
21675        .unwrap();
21676
21677    let multi_buffer = cx.new(|cx| {
21678        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21679        multi_buffer.push_excerpts(
21680            buffer_1.clone(),
21681            [
21682                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21683                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21684                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21685            ],
21686            cx,
21687        );
21688        multi_buffer.push_excerpts(
21689            buffer_2.clone(),
21690            [
21691                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21692                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21693                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21694            ],
21695            cx,
21696        );
21697        multi_buffer.push_excerpts(
21698            buffer_3.clone(),
21699            [
21700                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21701                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21702                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21703            ],
21704            cx,
21705        );
21706        multi_buffer
21707    });
21708    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21709        Editor::new(
21710            EditorMode::full(),
21711            multi_buffer.clone(),
21712            Some(project.clone()),
21713            window,
21714            cx,
21715        )
21716    });
21717
21718    assert_eq!(
21719        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21720        "\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",
21721    );
21722
21723    multi_buffer_editor.update(cx, |editor, cx| {
21724        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21725    });
21726    assert_eq!(
21727        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21728        "\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",
21729        "After folding the first buffer, its text should not be displayed"
21730    );
21731
21732    multi_buffer_editor.update(cx, |editor, cx| {
21733        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21734    });
21735    assert_eq!(
21736        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21737        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21738        "After folding the second buffer, its text should not be displayed"
21739    );
21740
21741    multi_buffer_editor.update(cx, |editor, cx| {
21742        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21743    });
21744    assert_eq!(
21745        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21746        "\n\n\n\n\n",
21747        "After folding the third buffer, its text should not be displayed"
21748    );
21749
21750    // Emulate selection inside the fold logic, that should work
21751    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21752        editor
21753            .snapshot(window, cx)
21754            .next_line_boundary(Point::new(0, 4));
21755    });
21756
21757    multi_buffer_editor.update(cx, |editor, cx| {
21758        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21759    });
21760    assert_eq!(
21761        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21762        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21763        "After unfolding the second buffer, its text should be displayed"
21764    );
21765
21766    // Typing inside of buffer 1 causes that buffer to be unfolded.
21767    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21768        assert_eq!(
21769            multi_buffer
21770                .read(cx)
21771                .snapshot(cx)
21772                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21773                .collect::<String>(),
21774            "bbbb"
21775        );
21776        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21777            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
21778        });
21779        editor.handle_input("B", window, cx);
21780    });
21781
21782    assert_eq!(
21783        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21784        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21785        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
21786    );
21787
21788    multi_buffer_editor.update(cx, |editor, cx| {
21789        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21790    });
21791    assert_eq!(
21792        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21793        "\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",
21794        "After unfolding the all buffers, all original text should be displayed"
21795    );
21796}
21797
21798#[gpui::test]
21799async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
21800    init_test(cx, |_| {});
21801
21802    let sample_text_1 = "1111\n2222\n3333".to_string();
21803    let sample_text_2 = "4444\n5555\n6666".to_string();
21804    let sample_text_3 = "7777\n8888\n9999".to_string();
21805
21806    let fs = FakeFs::new(cx.executor());
21807    fs.insert_tree(
21808        path!("/a"),
21809        json!({
21810            "first.rs": sample_text_1,
21811            "second.rs": sample_text_2,
21812            "third.rs": sample_text_3,
21813        }),
21814    )
21815    .await;
21816    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21817    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21818    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21819    let worktree = project.update(cx, |project, cx| {
21820        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21821        assert_eq!(worktrees.len(), 1);
21822        worktrees.pop().unwrap()
21823    });
21824    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21825
21826    let buffer_1 = project
21827        .update(cx, |project, cx| {
21828            project.open_buffer((worktree_id, "first.rs"), cx)
21829        })
21830        .await
21831        .unwrap();
21832    let buffer_2 = project
21833        .update(cx, |project, cx| {
21834            project.open_buffer((worktree_id, "second.rs"), cx)
21835        })
21836        .await
21837        .unwrap();
21838    let buffer_3 = project
21839        .update(cx, |project, cx| {
21840            project.open_buffer((worktree_id, "third.rs"), cx)
21841        })
21842        .await
21843        .unwrap();
21844
21845    let multi_buffer = cx.new(|cx| {
21846        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21847        multi_buffer.push_excerpts(
21848            buffer_1.clone(),
21849            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21850            cx,
21851        );
21852        multi_buffer.push_excerpts(
21853            buffer_2.clone(),
21854            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21855            cx,
21856        );
21857        multi_buffer.push_excerpts(
21858            buffer_3.clone(),
21859            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21860            cx,
21861        );
21862        multi_buffer
21863    });
21864
21865    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21866        Editor::new(
21867            EditorMode::full(),
21868            multi_buffer,
21869            Some(project.clone()),
21870            window,
21871            cx,
21872        )
21873    });
21874
21875    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
21876    assert_eq!(
21877        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21878        full_text,
21879    );
21880
21881    multi_buffer_editor.update(cx, |editor, cx| {
21882        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21883    });
21884    assert_eq!(
21885        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21886        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
21887        "After folding the first buffer, its text should not be displayed"
21888    );
21889
21890    multi_buffer_editor.update(cx, |editor, cx| {
21891        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21892    });
21893
21894    assert_eq!(
21895        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21896        "\n\n\n\n\n\n7777\n8888\n9999",
21897        "After folding the second buffer, its text should not be displayed"
21898    );
21899
21900    multi_buffer_editor.update(cx, |editor, cx| {
21901        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21902    });
21903    assert_eq!(
21904        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21905        "\n\n\n\n\n",
21906        "After folding the third buffer, its text should not be displayed"
21907    );
21908
21909    multi_buffer_editor.update(cx, |editor, cx| {
21910        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21911    });
21912    assert_eq!(
21913        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21914        "\n\n\n\n4444\n5555\n6666\n\n",
21915        "After unfolding the second buffer, its text should be displayed"
21916    );
21917
21918    multi_buffer_editor.update(cx, |editor, cx| {
21919        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
21920    });
21921    assert_eq!(
21922        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21923        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
21924        "After unfolding the first buffer, its text should be displayed"
21925    );
21926
21927    multi_buffer_editor.update(cx, |editor, cx| {
21928        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21929    });
21930    assert_eq!(
21931        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21932        full_text,
21933        "After unfolding all buffers, all original text should be displayed"
21934    );
21935}
21936
21937#[gpui::test]
21938async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
21939    init_test(cx, |_| {});
21940
21941    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21942
21943    let fs = FakeFs::new(cx.executor());
21944    fs.insert_tree(
21945        path!("/a"),
21946        json!({
21947            "main.rs": sample_text,
21948        }),
21949    )
21950    .await;
21951    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21952    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21953    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21954    let worktree = project.update(cx, |project, cx| {
21955        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21956        assert_eq!(worktrees.len(), 1);
21957        worktrees.pop().unwrap()
21958    });
21959    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21960
21961    let buffer_1 = project
21962        .update(cx, |project, cx| {
21963            project.open_buffer((worktree_id, "main.rs"), cx)
21964        })
21965        .await
21966        .unwrap();
21967
21968    let multi_buffer = cx.new(|cx| {
21969        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21970        multi_buffer.push_excerpts(
21971            buffer_1.clone(),
21972            [ExcerptRange::new(
21973                Point::new(0, 0)
21974                    ..Point::new(
21975                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
21976                        0,
21977                    ),
21978            )],
21979            cx,
21980        );
21981        multi_buffer
21982    });
21983    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21984        Editor::new(
21985            EditorMode::full(),
21986            multi_buffer,
21987            Some(project.clone()),
21988            window,
21989            cx,
21990        )
21991    });
21992
21993    let selection_range = Point::new(1, 0)..Point::new(2, 0);
21994    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21995        enum TestHighlight {}
21996        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
21997        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
21998        editor.highlight_text::<TestHighlight>(
21999            vec![highlight_range.clone()],
22000            HighlightStyle::color(Hsla::green()),
22001            cx,
22002        );
22003        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22004            s.select_ranges(Some(highlight_range))
22005        });
22006    });
22007
22008    let full_text = format!("\n\n{sample_text}");
22009    assert_eq!(
22010        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22011        full_text,
22012    );
22013}
22014
22015#[gpui::test]
22016async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22017    init_test(cx, |_| {});
22018    cx.update(|cx| {
22019        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22020            "keymaps/default-linux.json",
22021            cx,
22022        )
22023        .unwrap();
22024        cx.bind_keys(default_key_bindings);
22025    });
22026
22027    let (editor, cx) = cx.add_window_view(|window, cx| {
22028        let multi_buffer = MultiBuffer::build_multi(
22029            [
22030                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22031                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22032                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22033                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22034            ],
22035            cx,
22036        );
22037        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22038
22039        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22040        // fold all but the second buffer, so that we test navigating between two
22041        // adjacent folded buffers, as well as folded buffers at the start and
22042        // end the multibuffer
22043        editor.fold_buffer(buffer_ids[0], cx);
22044        editor.fold_buffer(buffer_ids[2], cx);
22045        editor.fold_buffer(buffer_ids[3], cx);
22046
22047        editor
22048    });
22049    cx.simulate_resize(size(px(1000.), px(1000.)));
22050
22051    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22052    cx.assert_excerpts_with_selections(indoc! {"
22053        [EXCERPT]
22054        ˇ[FOLDED]
22055        [EXCERPT]
22056        a1
22057        b1
22058        [EXCERPT]
22059        [FOLDED]
22060        [EXCERPT]
22061        [FOLDED]
22062        "
22063    });
22064    cx.simulate_keystroke("down");
22065    cx.assert_excerpts_with_selections(indoc! {"
22066        [EXCERPT]
22067        [FOLDED]
22068        [EXCERPT]
22069        ˇa1
22070        b1
22071        [EXCERPT]
22072        [FOLDED]
22073        [EXCERPT]
22074        [FOLDED]
22075        "
22076    });
22077    cx.simulate_keystroke("down");
22078    cx.assert_excerpts_with_selections(indoc! {"
22079        [EXCERPT]
22080        [FOLDED]
22081        [EXCERPT]
22082        a1
22083        ˇb1
22084        [EXCERPT]
22085        [FOLDED]
22086        [EXCERPT]
22087        [FOLDED]
22088        "
22089    });
22090    cx.simulate_keystroke("down");
22091    cx.assert_excerpts_with_selections(indoc! {"
22092        [EXCERPT]
22093        [FOLDED]
22094        [EXCERPT]
22095        a1
22096        b1
22097        ˇ[EXCERPT]
22098        [FOLDED]
22099        [EXCERPT]
22100        [FOLDED]
22101        "
22102    });
22103    cx.simulate_keystroke("down");
22104    cx.assert_excerpts_with_selections(indoc! {"
22105        [EXCERPT]
22106        [FOLDED]
22107        [EXCERPT]
22108        a1
22109        b1
22110        [EXCERPT]
22111        ˇ[FOLDED]
22112        [EXCERPT]
22113        [FOLDED]
22114        "
22115    });
22116    for _ in 0..5 {
22117        cx.simulate_keystroke("down");
22118        cx.assert_excerpts_with_selections(indoc! {"
22119            [EXCERPT]
22120            [FOLDED]
22121            [EXCERPT]
22122            a1
22123            b1
22124            [EXCERPT]
22125            [FOLDED]
22126            [EXCERPT]
22127            ˇ[FOLDED]
22128            "
22129        });
22130    }
22131
22132    cx.simulate_keystroke("up");
22133    cx.assert_excerpts_with_selections(indoc! {"
22134        [EXCERPT]
22135        [FOLDED]
22136        [EXCERPT]
22137        a1
22138        b1
22139        [EXCERPT]
22140        ˇ[FOLDED]
22141        [EXCERPT]
22142        [FOLDED]
22143        "
22144    });
22145    cx.simulate_keystroke("up");
22146    cx.assert_excerpts_with_selections(indoc! {"
22147        [EXCERPT]
22148        [FOLDED]
22149        [EXCERPT]
22150        a1
22151        b1
22152        ˇ[EXCERPT]
22153        [FOLDED]
22154        [EXCERPT]
22155        [FOLDED]
22156        "
22157    });
22158    cx.simulate_keystroke("up");
22159    cx.assert_excerpts_with_selections(indoc! {"
22160        [EXCERPT]
22161        [FOLDED]
22162        [EXCERPT]
22163        a1
22164        ˇb1
22165        [EXCERPT]
22166        [FOLDED]
22167        [EXCERPT]
22168        [FOLDED]
22169        "
22170    });
22171    cx.simulate_keystroke("up");
22172    cx.assert_excerpts_with_selections(indoc! {"
22173        [EXCERPT]
22174        [FOLDED]
22175        [EXCERPT]
22176        ˇa1
22177        b1
22178        [EXCERPT]
22179        [FOLDED]
22180        [EXCERPT]
22181        [FOLDED]
22182        "
22183    });
22184    for _ in 0..5 {
22185        cx.simulate_keystroke("up");
22186        cx.assert_excerpts_with_selections(indoc! {"
22187            [EXCERPT]
22188            ˇ[FOLDED]
22189            [EXCERPT]
22190            a1
22191            b1
22192            [EXCERPT]
22193            [FOLDED]
22194            [EXCERPT]
22195            [FOLDED]
22196            "
22197        });
22198    }
22199}
22200
22201#[gpui::test]
22202async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22203    init_test(cx, |_| {});
22204
22205    // Simple insertion
22206    assert_highlighted_edits(
22207        "Hello, world!",
22208        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22209        true,
22210        cx,
22211        |highlighted_edits, cx| {
22212            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22213            assert_eq!(highlighted_edits.highlights.len(), 1);
22214            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22215            assert_eq!(
22216                highlighted_edits.highlights[0].1.background_color,
22217                Some(cx.theme().status().created_background)
22218            );
22219        },
22220    )
22221    .await;
22222
22223    // Replacement
22224    assert_highlighted_edits(
22225        "This is a test.",
22226        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22227        false,
22228        cx,
22229        |highlighted_edits, cx| {
22230            assert_eq!(highlighted_edits.text, "That is a test.");
22231            assert_eq!(highlighted_edits.highlights.len(), 1);
22232            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22233            assert_eq!(
22234                highlighted_edits.highlights[0].1.background_color,
22235                Some(cx.theme().status().created_background)
22236            );
22237        },
22238    )
22239    .await;
22240
22241    // Multiple edits
22242    assert_highlighted_edits(
22243        "Hello, world!",
22244        vec![
22245            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22246            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22247        ],
22248        false,
22249        cx,
22250        |highlighted_edits, cx| {
22251            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22252            assert_eq!(highlighted_edits.highlights.len(), 2);
22253            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22254            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22255            assert_eq!(
22256                highlighted_edits.highlights[0].1.background_color,
22257                Some(cx.theme().status().created_background)
22258            );
22259            assert_eq!(
22260                highlighted_edits.highlights[1].1.background_color,
22261                Some(cx.theme().status().created_background)
22262            );
22263        },
22264    )
22265    .await;
22266
22267    // Multiple lines with edits
22268    assert_highlighted_edits(
22269        "First line\nSecond line\nThird line\nFourth line",
22270        vec![
22271            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22272            (
22273                Point::new(2, 0)..Point::new(2, 10),
22274                "New third line".to_string(),
22275            ),
22276            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22277        ],
22278        false,
22279        cx,
22280        |highlighted_edits, cx| {
22281            assert_eq!(
22282                highlighted_edits.text,
22283                "Second modified\nNew third line\nFourth updated line"
22284            );
22285            assert_eq!(highlighted_edits.highlights.len(), 3);
22286            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22287            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22288            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22289            for highlight in &highlighted_edits.highlights {
22290                assert_eq!(
22291                    highlight.1.background_color,
22292                    Some(cx.theme().status().created_background)
22293                );
22294            }
22295        },
22296    )
22297    .await;
22298}
22299
22300#[gpui::test]
22301async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22302    init_test(cx, |_| {});
22303
22304    // Deletion
22305    assert_highlighted_edits(
22306        "Hello, world!",
22307        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22308        true,
22309        cx,
22310        |highlighted_edits, cx| {
22311            assert_eq!(highlighted_edits.text, "Hello, world!");
22312            assert_eq!(highlighted_edits.highlights.len(), 1);
22313            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22314            assert_eq!(
22315                highlighted_edits.highlights[0].1.background_color,
22316                Some(cx.theme().status().deleted_background)
22317            );
22318        },
22319    )
22320    .await;
22321
22322    // Insertion
22323    assert_highlighted_edits(
22324        "Hello, world!",
22325        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22326        true,
22327        cx,
22328        |highlighted_edits, cx| {
22329            assert_eq!(highlighted_edits.highlights.len(), 1);
22330            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22331            assert_eq!(
22332                highlighted_edits.highlights[0].1.background_color,
22333                Some(cx.theme().status().created_background)
22334            );
22335        },
22336    )
22337    .await;
22338}
22339
22340async fn assert_highlighted_edits(
22341    text: &str,
22342    edits: Vec<(Range<Point>, String)>,
22343    include_deletions: bool,
22344    cx: &mut TestAppContext,
22345    assertion_fn: impl Fn(HighlightedText, &App),
22346) {
22347    let window = cx.add_window(|window, cx| {
22348        let buffer = MultiBuffer::build_simple(text, cx);
22349        Editor::new(EditorMode::full(), buffer, None, window, cx)
22350    });
22351    let cx = &mut VisualTestContext::from_window(*window, cx);
22352
22353    let (buffer, snapshot) = window
22354        .update(cx, |editor, _window, cx| {
22355            (
22356                editor.buffer().clone(),
22357                editor.buffer().read(cx).snapshot(cx),
22358            )
22359        })
22360        .unwrap();
22361
22362    let edits = edits
22363        .into_iter()
22364        .map(|(range, edit)| {
22365            (
22366                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22367                edit,
22368            )
22369        })
22370        .collect::<Vec<_>>();
22371
22372    let text_anchor_edits = edits
22373        .clone()
22374        .into_iter()
22375        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22376        .collect::<Vec<_>>();
22377
22378    let edit_preview = window
22379        .update(cx, |_, _window, cx| {
22380            buffer
22381                .read(cx)
22382                .as_singleton()
22383                .unwrap()
22384                .read(cx)
22385                .preview_edits(text_anchor_edits.into(), cx)
22386        })
22387        .unwrap()
22388        .await;
22389
22390    cx.update(|_window, cx| {
22391        let highlighted_edits = edit_prediction_edit_text(
22392            snapshot.as_singleton().unwrap().2,
22393            &edits,
22394            &edit_preview,
22395            include_deletions,
22396            cx,
22397        );
22398        assertion_fn(highlighted_edits, cx)
22399    });
22400}
22401
22402#[track_caller]
22403fn assert_breakpoint(
22404    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22405    path: &Arc<Path>,
22406    expected: Vec<(u32, Breakpoint)>,
22407) {
22408    if expected.is_empty() {
22409        assert!(!breakpoints.contains_key(path), "{}", path.display());
22410    } else {
22411        let mut breakpoint = breakpoints
22412            .get(path)
22413            .unwrap()
22414            .iter()
22415            .map(|breakpoint| {
22416                (
22417                    breakpoint.row,
22418                    Breakpoint {
22419                        message: breakpoint.message.clone(),
22420                        state: breakpoint.state,
22421                        condition: breakpoint.condition.clone(),
22422                        hit_condition: breakpoint.hit_condition.clone(),
22423                    },
22424                )
22425            })
22426            .collect::<Vec<_>>();
22427
22428        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22429
22430        assert_eq!(expected, breakpoint);
22431    }
22432}
22433
22434fn add_log_breakpoint_at_cursor(
22435    editor: &mut Editor,
22436    log_message: &str,
22437    window: &mut Window,
22438    cx: &mut Context<Editor>,
22439) {
22440    let (anchor, bp) = editor
22441        .breakpoints_at_cursors(window, cx)
22442        .first()
22443        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22444        .unwrap_or_else(|| {
22445            let cursor_position: Point = editor.selections.newest(cx).head();
22446
22447            let breakpoint_position = editor
22448                .snapshot(window, cx)
22449                .display_snapshot
22450                .buffer_snapshot
22451                .anchor_before(Point::new(cursor_position.row, 0));
22452
22453            (breakpoint_position, Breakpoint::new_log(log_message))
22454        });
22455
22456    editor.edit_breakpoint_at_anchor(
22457        anchor,
22458        bp,
22459        BreakpointEditAction::EditLogMessage(log_message.into()),
22460        cx,
22461    );
22462}
22463
22464#[gpui::test]
22465async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22466    init_test(cx, |_| {});
22467
22468    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22469    let fs = FakeFs::new(cx.executor());
22470    fs.insert_tree(
22471        path!("/a"),
22472        json!({
22473            "main.rs": sample_text,
22474        }),
22475    )
22476    .await;
22477    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22478    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22479    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22480
22481    let fs = FakeFs::new(cx.executor());
22482    fs.insert_tree(
22483        path!("/a"),
22484        json!({
22485            "main.rs": sample_text,
22486        }),
22487    )
22488    .await;
22489    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22490    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22491    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22492    let worktree_id = workspace
22493        .update(cx, |workspace, _window, cx| {
22494            workspace.project().update(cx, |project, cx| {
22495                project.worktrees(cx).next().unwrap().read(cx).id()
22496            })
22497        })
22498        .unwrap();
22499
22500    let buffer = project
22501        .update(cx, |project, cx| {
22502            project.open_buffer((worktree_id, "main.rs"), cx)
22503        })
22504        .await
22505        .unwrap();
22506
22507    let (editor, cx) = cx.add_window_view(|window, cx| {
22508        Editor::new(
22509            EditorMode::full(),
22510            MultiBuffer::build_from_buffer(buffer, cx),
22511            Some(project.clone()),
22512            window,
22513            cx,
22514        )
22515    });
22516
22517    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22518    let abs_path = project.read_with(cx, |project, cx| {
22519        project
22520            .absolute_path(&project_path, cx)
22521            .map(Arc::from)
22522            .unwrap()
22523    });
22524
22525    // assert we can add breakpoint on the first line
22526    editor.update_in(cx, |editor, window, cx| {
22527        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22528        editor.move_to_end(&MoveToEnd, window, cx);
22529        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22530    });
22531
22532    let breakpoints = editor.update(cx, |editor, cx| {
22533        editor
22534            .breakpoint_store()
22535            .as_ref()
22536            .unwrap()
22537            .read(cx)
22538            .all_source_breakpoints(cx)
22539    });
22540
22541    assert_eq!(1, breakpoints.len());
22542    assert_breakpoint(
22543        &breakpoints,
22544        &abs_path,
22545        vec![
22546            (0, Breakpoint::new_standard()),
22547            (3, Breakpoint::new_standard()),
22548        ],
22549    );
22550
22551    editor.update_in(cx, |editor, window, cx| {
22552        editor.move_to_beginning(&MoveToBeginning, window, cx);
22553        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22554    });
22555
22556    let breakpoints = editor.update(cx, |editor, cx| {
22557        editor
22558            .breakpoint_store()
22559            .as_ref()
22560            .unwrap()
22561            .read(cx)
22562            .all_source_breakpoints(cx)
22563    });
22564
22565    assert_eq!(1, breakpoints.len());
22566    assert_breakpoint(
22567        &breakpoints,
22568        &abs_path,
22569        vec![(3, Breakpoint::new_standard())],
22570    );
22571
22572    editor.update_in(cx, |editor, window, cx| {
22573        editor.move_to_end(&MoveToEnd, window, cx);
22574        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22575    });
22576
22577    let breakpoints = editor.update(cx, |editor, cx| {
22578        editor
22579            .breakpoint_store()
22580            .as_ref()
22581            .unwrap()
22582            .read(cx)
22583            .all_source_breakpoints(cx)
22584    });
22585
22586    assert_eq!(0, breakpoints.len());
22587    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22588}
22589
22590#[gpui::test]
22591async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22592    init_test(cx, |_| {});
22593
22594    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22595
22596    let fs = FakeFs::new(cx.executor());
22597    fs.insert_tree(
22598        path!("/a"),
22599        json!({
22600            "main.rs": sample_text,
22601        }),
22602    )
22603    .await;
22604    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22605    let (workspace, cx) =
22606        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22607
22608    let worktree_id = workspace.update(cx, |workspace, cx| {
22609        workspace.project().update(cx, |project, cx| {
22610            project.worktrees(cx).next().unwrap().read(cx).id()
22611        })
22612    });
22613
22614    let buffer = project
22615        .update(cx, |project, cx| {
22616            project.open_buffer((worktree_id, "main.rs"), cx)
22617        })
22618        .await
22619        .unwrap();
22620
22621    let (editor, cx) = cx.add_window_view(|window, cx| {
22622        Editor::new(
22623            EditorMode::full(),
22624            MultiBuffer::build_from_buffer(buffer, cx),
22625            Some(project.clone()),
22626            window,
22627            cx,
22628        )
22629    });
22630
22631    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22632    let abs_path = project.read_with(cx, |project, cx| {
22633        project
22634            .absolute_path(&project_path, cx)
22635            .map(Arc::from)
22636            .unwrap()
22637    });
22638
22639    editor.update_in(cx, |editor, window, cx| {
22640        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22641    });
22642
22643    let breakpoints = editor.update(cx, |editor, cx| {
22644        editor
22645            .breakpoint_store()
22646            .as_ref()
22647            .unwrap()
22648            .read(cx)
22649            .all_source_breakpoints(cx)
22650    });
22651
22652    assert_breakpoint(
22653        &breakpoints,
22654        &abs_path,
22655        vec![(0, Breakpoint::new_log("hello world"))],
22656    );
22657
22658    // Removing a log message from a log breakpoint should remove it
22659    editor.update_in(cx, |editor, window, cx| {
22660        add_log_breakpoint_at_cursor(editor, "", window, cx);
22661    });
22662
22663    let breakpoints = editor.update(cx, |editor, cx| {
22664        editor
22665            .breakpoint_store()
22666            .as_ref()
22667            .unwrap()
22668            .read(cx)
22669            .all_source_breakpoints(cx)
22670    });
22671
22672    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22673
22674    editor.update_in(cx, |editor, window, cx| {
22675        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22676        editor.move_to_end(&MoveToEnd, window, cx);
22677        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22678        // Not adding a log message to a standard breakpoint shouldn't remove it
22679        add_log_breakpoint_at_cursor(editor, "", window, cx);
22680    });
22681
22682    let breakpoints = editor.update(cx, |editor, cx| {
22683        editor
22684            .breakpoint_store()
22685            .as_ref()
22686            .unwrap()
22687            .read(cx)
22688            .all_source_breakpoints(cx)
22689    });
22690
22691    assert_breakpoint(
22692        &breakpoints,
22693        &abs_path,
22694        vec![
22695            (0, Breakpoint::new_standard()),
22696            (3, Breakpoint::new_standard()),
22697        ],
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![
22717            (0, Breakpoint::new_standard()),
22718            (3, Breakpoint::new_log("hello world")),
22719        ],
22720    );
22721
22722    editor.update_in(cx, |editor, window, cx| {
22723        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22724    });
22725
22726    let breakpoints = editor.update(cx, |editor, cx| {
22727        editor
22728            .breakpoint_store()
22729            .as_ref()
22730            .unwrap()
22731            .read(cx)
22732            .all_source_breakpoints(cx)
22733    });
22734
22735    assert_breakpoint(
22736        &breakpoints,
22737        &abs_path,
22738        vec![
22739            (0, Breakpoint::new_standard()),
22740            (3, Breakpoint::new_log("hello Earth!!")),
22741        ],
22742    );
22743}
22744
22745/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22746/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22747/// or when breakpoints were placed out of order. This tests for a regression too
22748#[gpui::test]
22749async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22750    init_test(cx, |_| {});
22751
22752    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22753    let fs = FakeFs::new(cx.executor());
22754    fs.insert_tree(
22755        path!("/a"),
22756        json!({
22757            "main.rs": sample_text,
22758        }),
22759    )
22760    .await;
22761    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22762    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22763    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22764
22765    let fs = FakeFs::new(cx.executor());
22766    fs.insert_tree(
22767        path!("/a"),
22768        json!({
22769            "main.rs": sample_text,
22770        }),
22771    )
22772    .await;
22773    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22774    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22775    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22776    let worktree_id = workspace
22777        .update(cx, |workspace, _window, cx| {
22778            workspace.project().update(cx, |project, cx| {
22779                project.worktrees(cx).next().unwrap().read(cx).id()
22780            })
22781        })
22782        .unwrap();
22783
22784    let buffer = project
22785        .update(cx, |project, cx| {
22786            project.open_buffer((worktree_id, "main.rs"), cx)
22787        })
22788        .await
22789        .unwrap();
22790
22791    let (editor, cx) = cx.add_window_view(|window, cx| {
22792        Editor::new(
22793            EditorMode::full(),
22794            MultiBuffer::build_from_buffer(buffer, cx),
22795            Some(project.clone()),
22796            window,
22797            cx,
22798        )
22799    });
22800
22801    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22802    let abs_path = project.read_with(cx, |project, cx| {
22803        project
22804            .absolute_path(&project_path, cx)
22805            .map(Arc::from)
22806            .unwrap()
22807    });
22808
22809    // assert we can add breakpoint on the first line
22810    editor.update_in(cx, |editor, window, cx| {
22811        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22812        editor.move_to_end(&MoveToEnd, window, cx);
22813        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22814        editor.move_up(&MoveUp, window, cx);
22815        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22816    });
22817
22818    let breakpoints = editor.update(cx, |editor, cx| {
22819        editor
22820            .breakpoint_store()
22821            .as_ref()
22822            .unwrap()
22823            .read(cx)
22824            .all_source_breakpoints(cx)
22825    });
22826
22827    assert_eq!(1, breakpoints.len());
22828    assert_breakpoint(
22829        &breakpoints,
22830        &abs_path,
22831        vec![
22832            (0, Breakpoint::new_standard()),
22833            (2, Breakpoint::new_standard()),
22834            (3, Breakpoint::new_standard()),
22835        ],
22836    );
22837
22838    editor.update_in(cx, |editor, window, cx| {
22839        editor.move_to_beginning(&MoveToBeginning, window, cx);
22840        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22841        editor.move_to_end(&MoveToEnd, window, cx);
22842        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22843        // Disabling a breakpoint that doesn't exist should do nothing
22844        editor.move_up(&MoveUp, window, cx);
22845        editor.move_up(&MoveUp, window, cx);
22846        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22847    });
22848
22849    let breakpoints = editor.update(cx, |editor, cx| {
22850        editor
22851            .breakpoint_store()
22852            .as_ref()
22853            .unwrap()
22854            .read(cx)
22855            .all_source_breakpoints(cx)
22856    });
22857
22858    let disable_breakpoint = {
22859        let mut bp = Breakpoint::new_standard();
22860        bp.state = BreakpointState::Disabled;
22861        bp
22862    };
22863
22864    assert_eq!(1, breakpoints.len());
22865    assert_breakpoint(
22866        &breakpoints,
22867        &abs_path,
22868        vec![
22869            (0, disable_breakpoint.clone()),
22870            (2, Breakpoint::new_standard()),
22871            (3, disable_breakpoint.clone()),
22872        ],
22873    );
22874
22875    editor.update_in(cx, |editor, window, cx| {
22876        editor.move_to_beginning(&MoveToBeginning, window, cx);
22877        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22878        editor.move_to_end(&MoveToEnd, window, cx);
22879        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22880        editor.move_up(&MoveUp, window, cx);
22881        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22882    });
22883
22884    let breakpoints = editor.update(cx, |editor, cx| {
22885        editor
22886            .breakpoint_store()
22887            .as_ref()
22888            .unwrap()
22889            .read(cx)
22890            .all_source_breakpoints(cx)
22891    });
22892
22893    assert_eq!(1, breakpoints.len());
22894    assert_breakpoint(
22895        &breakpoints,
22896        &abs_path,
22897        vec![
22898            (0, Breakpoint::new_standard()),
22899            (2, disable_breakpoint),
22900            (3, Breakpoint::new_standard()),
22901        ],
22902    );
22903}
22904
22905#[gpui::test]
22906async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
22907    init_test(cx, |_| {});
22908    let capabilities = lsp::ServerCapabilities {
22909        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
22910            prepare_provider: Some(true),
22911            work_done_progress_options: Default::default(),
22912        })),
22913        ..Default::default()
22914    };
22915    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22916
22917    cx.set_state(indoc! {"
22918        struct Fˇoo {}
22919    "});
22920
22921    cx.update_editor(|editor, _, cx| {
22922        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22923        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22924        editor.highlight_background::<DocumentHighlightRead>(
22925            &[highlight_range],
22926            |theme| theme.colors().editor_document_highlight_read_background,
22927            cx,
22928        );
22929    });
22930
22931    let mut prepare_rename_handler = cx
22932        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
22933            move |_, _, _| async move {
22934                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
22935                    start: lsp::Position {
22936                        line: 0,
22937                        character: 7,
22938                    },
22939                    end: lsp::Position {
22940                        line: 0,
22941                        character: 10,
22942                    },
22943                })))
22944            },
22945        );
22946    let prepare_rename_task = cx
22947        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
22948        .expect("Prepare rename was not started");
22949    prepare_rename_handler.next().await.unwrap();
22950    prepare_rename_task.await.expect("Prepare rename failed");
22951
22952    let mut rename_handler =
22953        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
22954            let edit = lsp::TextEdit {
22955                range: lsp::Range {
22956                    start: lsp::Position {
22957                        line: 0,
22958                        character: 7,
22959                    },
22960                    end: lsp::Position {
22961                        line: 0,
22962                        character: 10,
22963                    },
22964                },
22965                new_text: "FooRenamed".to_string(),
22966            };
22967            Ok(Some(lsp::WorkspaceEdit::new(
22968                // Specify the same edit twice
22969                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
22970            )))
22971        });
22972    let rename_task = cx
22973        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
22974        .expect("Confirm rename was not started");
22975    rename_handler.next().await.unwrap();
22976    rename_task.await.expect("Confirm rename failed");
22977    cx.run_until_parked();
22978
22979    // Despite two edits, only one is actually applied as those are identical
22980    cx.assert_editor_state(indoc! {"
22981        struct FooRenamedˇ {}
22982    "});
22983}
22984
22985#[gpui::test]
22986async fn test_rename_without_prepare(cx: &mut TestAppContext) {
22987    init_test(cx, |_| {});
22988    // These capabilities indicate that the server does not support prepare rename.
22989    let capabilities = lsp::ServerCapabilities {
22990        rename_provider: Some(lsp::OneOf::Left(true)),
22991        ..Default::default()
22992    };
22993    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22994
22995    cx.set_state(indoc! {"
22996        struct Fˇoo {}
22997    "});
22998
22999    cx.update_editor(|editor, _window, cx| {
23000        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23001        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23002        editor.highlight_background::<DocumentHighlightRead>(
23003            &[highlight_range],
23004            |theme| theme.colors().editor_document_highlight_read_background,
23005            cx,
23006        );
23007    });
23008
23009    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23010        .expect("Prepare rename was not started")
23011        .await
23012        .expect("Prepare rename failed");
23013
23014    let mut rename_handler =
23015        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23016            let edit = lsp::TextEdit {
23017                range: lsp::Range {
23018                    start: lsp::Position {
23019                        line: 0,
23020                        character: 7,
23021                    },
23022                    end: lsp::Position {
23023                        line: 0,
23024                        character: 10,
23025                    },
23026                },
23027                new_text: "FooRenamed".to_string(),
23028            };
23029            Ok(Some(lsp::WorkspaceEdit::new(
23030                std::collections::HashMap::from_iter(Some((url, vec![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    // Correct range is renamed, as `surrounding_word` is used to find it.
23041    cx.assert_editor_state(indoc! {"
23042        struct FooRenamedˇ {}
23043    "});
23044}
23045
23046#[gpui::test]
23047async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23048    init_test(cx, |_| {});
23049    let mut cx = EditorTestContext::new(cx).await;
23050
23051    let language = Arc::new(
23052        Language::new(
23053            LanguageConfig::default(),
23054            Some(tree_sitter_html::LANGUAGE.into()),
23055        )
23056        .with_brackets_query(
23057            r#"
23058            ("<" @open "/>" @close)
23059            ("</" @open ">" @close)
23060            ("<" @open ">" @close)
23061            ("\"" @open "\"" @close)
23062            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23063        "#,
23064        )
23065        .unwrap(),
23066    );
23067    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23068
23069    cx.set_state(indoc! {"
23070        <span>ˇ</span>
23071    "});
23072    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23073    cx.assert_editor_state(indoc! {"
23074        <span>
23075        ˇ
23076        </span>
23077    "});
23078
23079    cx.set_state(indoc! {"
23080        <span><span></span>ˇ</span>
23081    "});
23082    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23083    cx.assert_editor_state(indoc! {"
23084        <span><span></span>
23085        ˇ</span>
23086    "});
23087
23088    cx.set_state(indoc! {"
23089        <span>ˇ
23090        </span>
23091    "});
23092    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23093    cx.assert_editor_state(indoc! {"
23094        <span>
23095        ˇ
23096        </span>
23097    "});
23098}
23099
23100#[gpui::test(iterations = 10)]
23101async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23102    init_test(cx, |_| {});
23103
23104    let fs = FakeFs::new(cx.executor());
23105    fs.insert_tree(
23106        path!("/dir"),
23107        json!({
23108            "a.ts": "a",
23109        }),
23110    )
23111    .await;
23112
23113    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23114    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23115    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23116
23117    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23118    language_registry.add(Arc::new(Language::new(
23119        LanguageConfig {
23120            name: "TypeScript".into(),
23121            matcher: LanguageMatcher {
23122                path_suffixes: vec!["ts".to_string()],
23123                ..Default::default()
23124            },
23125            ..Default::default()
23126        },
23127        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23128    )));
23129    let mut fake_language_servers = language_registry.register_fake_lsp(
23130        "TypeScript",
23131        FakeLspAdapter {
23132            capabilities: lsp::ServerCapabilities {
23133                code_lens_provider: Some(lsp::CodeLensOptions {
23134                    resolve_provider: Some(true),
23135                }),
23136                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23137                    commands: vec!["_the/command".to_string()],
23138                    ..lsp::ExecuteCommandOptions::default()
23139                }),
23140                ..lsp::ServerCapabilities::default()
23141            },
23142            ..FakeLspAdapter::default()
23143        },
23144    );
23145
23146    let editor = workspace
23147        .update(cx, |workspace, window, cx| {
23148            workspace.open_abs_path(
23149                PathBuf::from(path!("/dir/a.ts")),
23150                OpenOptions::default(),
23151                window,
23152                cx,
23153            )
23154        })
23155        .unwrap()
23156        .await
23157        .unwrap()
23158        .downcast::<Editor>()
23159        .unwrap();
23160    cx.executor().run_until_parked();
23161
23162    let fake_server = fake_language_servers.next().await.unwrap();
23163
23164    let buffer = editor.update(cx, |editor, cx| {
23165        editor
23166            .buffer()
23167            .read(cx)
23168            .as_singleton()
23169            .expect("have opened a single file by path")
23170    });
23171
23172    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23173    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23174    drop(buffer_snapshot);
23175    let actions = cx
23176        .update_window(*workspace, |_, window, cx| {
23177            project.code_actions(&buffer, anchor..anchor, window, cx)
23178        })
23179        .unwrap();
23180
23181    fake_server
23182        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23183            Ok(Some(vec![
23184                lsp::CodeLens {
23185                    range: lsp::Range::default(),
23186                    command: Some(lsp::Command {
23187                        title: "Code lens command".to_owned(),
23188                        command: "_the/command".to_owned(),
23189                        arguments: None,
23190                    }),
23191                    data: None,
23192                },
23193                lsp::CodeLens {
23194                    range: lsp::Range::default(),
23195                    command: Some(lsp::Command {
23196                        title: "Command not in capabilities".to_owned(),
23197                        command: "not in capabilities".to_owned(),
23198                        arguments: None,
23199                    }),
23200                    data: None,
23201                },
23202                lsp::CodeLens {
23203                    range: lsp::Range {
23204                        start: lsp::Position {
23205                            line: 1,
23206                            character: 1,
23207                        },
23208                        end: lsp::Position {
23209                            line: 1,
23210                            character: 1,
23211                        },
23212                    },
23213                    command: Some(lsp::Command {
23214                        title: "Command not in range".to_owned(),
23215                        command: "_the/command".to_owned(),
23216                        arguments: None,
23217                    }),
23218                    data: None,
23219                },
23220            ]))
23221        })
23222        .next()
23223        .await;
23224
23225    let actions = actions.await.unwrap();
23226    assert_eq!(
23227        actions.len(),
23228        1,
23229        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23230    );
23231    let action = actions[0].clone();
23232    let apply = project.update(cx, |project, cx| {
23233        project.apply_code_action(buffer.clone(), action, true, cx)
23234    });
23235
23236    // Resolving the code action does not populate its edits. In absence of
23237    // edits, we must execute the given command.
23238    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23239        |mut lens, _| async move {
23240            let lens_command = lens.command.as_mut().expect("should have a command");
23241            assert_eq!(lens_command.title, "Code lens command");
23242            lens_command.arguments = Some(vec![json!("the-argument")]);
23243            Ok(lens)
23244        },
23245    );
23246
23247    // While executing the command, the language server sends the editor
23248    // a `workspaceEdit` request.
23249    fake_server
23250        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23251            let fake = fake_server.clone();
23252            move |params, _| {
23253                assert_eq!(params.command, "_the/command");
23254                let fake = fake.clone();
23255                async move {
23256                    fake.server
23257                        .request::<lsp::request::ApplyWorkspaceEdit>(
23258                            lsp::ApplyWorkspaceEditParams {
23259                                label: None,
23260                                edit: lsp::WorkspaceEdit {
23261                                    changes: Some(
23262                                        [(
23263                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23264                                            vec![lsp::TextEdit {
23265                                                range: lsp::Range::new(
23266                                                    lsp::Position::new(0, 0),
23267                                                    lsp::Position::new(0, 0),
23268                                                ),
23269                                                new_text: "X".into(),
23270                                            }],
23271                                        )]
23272                                        .into_iter()
23273                                        .collect(),
23274                                    ),
23275                                    ..lsp::WorkspaceEdit::default()
23276                                },
23277                            },
23278                        )
23279                        .await
23280                        .into_response()
23281                        .unwrap();
23282                    Ok(Some(json!(null)))
23283                }
23284            }
23285        })
23286        .next()
23287        .await;
23288
23289    // Applying the code lens command returns a project transaction containing the edits
23290    // sent by the language server in its `workspaceEdit` request.
23291    let transaction = apply.await.unwrap();
23292    assert!(transaction.0.contains_key(&buffer));
23293    buffer.update(cx, |buffer, cx| {
23294        assert_eq!(buffer.text(), "Xa");
23295        buffer.undo(cx);
23296        assert_eq!(buffer.text(), "a");
23297    });
23298
23299    let actions_after_edits = cx
23300        .update_window(*workspace, |_, window, cx| {
23301            project.code_actions(&buffer, anchor..anchor, window, cx)
23302        })
23303        .unwrap()
23304        .await
23305        .unwrap();
23306    assert_eq!(
23307        actions, actions_after_edits,
23308        "For the same selection, same code lens actions should be returned"
23309    );
23310
23311    let _responses =
23312        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23313            panic!("No more code lens requests are expected");
23314        });
23315    editor.update_in(cx, |editor, window, cx| {
23316        editor.select_all(&SelectAll, window, cx);
23317    });
23318    cx.executor().run_until_parked();
23319    let new_actions = cx
23320        .update_window(*workspace, |_, window, cx| {
23321            project.code_actions(&buffer, anchor..anchor, window, cx)
23322        })
23323        .unwrap()
23324        .await
23325        .unwrap();
23326    assert_eq!(
23327        actions, new_actions,
23328        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23329    );
23330}
23331
23332#[gpui::test]
23333async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23334    init_test(cx, |_| {});
23335
23336    let fs = FakeFs::new(cx.executor());
23337    let main_text = r#"fn main() {
23338println!("1");
23339println!("2");
23340println!("3");
23341println!("4");
23342println!("5");
23343}"#;
23344    let lib_text = "mod foo {}";
23345    fs.insert_tree(
23346        path!("/a"),
23347        json!({
23348            "lib.rs": lib_text,
23349            "main.rs": main_text,
23350        }),
23351    )
23352    .await;
23353
23354    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23355    let (workspace, cx) =
23356        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23357    let worktree_id = workspace.update(cx, |workspace, cx| {
23358        workspace.project().update(cx, |project, cx| {
23359            project.worktrees(cx).next().unwrap().read(cx).id()
23360        })
23361    });
23362
23363    let expected_ranges = vec![
23364        Point::new(0, 0)..Point::new(0, 0),
23365        Point::new(1, 0)..Point::new(1, 1),
23366        Point::new(2, 0)..Point::new(2, 2),
23367        Point::new(3, 0)..Point::new(3, 3),
23368    ];
23369
23370    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23371    let editor_1 = workspace
23372        .update_in(cx, |workspace, window, cx| {
23373            workspace.open_path(
23374                (worktree_id, "main.rs"),
23375                Some(pane_1.downgrade()),
23376                true,
23377                window,
23378                cx,
23379            )
23380        })
23381        .unwrap()
23382        .await
23383        .downcast::<Editor>()
23384        .unwrap();
23385    pane_1.update(cx, |pane, cx| {
23386        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23387        open_editor.update(cx, |editor, cx| {
23388            assert_eq!(
23389                editor.display_text(cx),
23390                main_text,
23391                "Original main.rs text on initial open",
23392            );
23393            assert_eq!(
23394                editor
23395                    .selections
23396                    .all::<Point>(cx)
23397                    .into_iter()
23398                    .map(|s| s.range())
23399                    .collect::<Vec<_>>(),
23400                vec![Point::zero()..Point::zero()],
23401                "Default selections on initial open",
23402            );
23403        })
23404    });
23405    editor_1.update_in(cx, |editor, window, cx| {
23406        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23407            s.select_ranges(expected_ranges.clone());
23408        });
23409    });
23410
23411    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23412        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23413    });
23414    let editor_2 = workspace
23415        .update_in(cx, |workspace, window, cx| {
23416            workspace.open_path(
23417                (worktree_id, "main.rs"),
23418                Some(pane_2.downgrade()),
23419                true,
23420                window,
23421                cx,
23422            )
23423        })
23424        .unwrap()
23425        .await
23426        .downcast::<Editor>()
23427        .unwrap();
23428    pane_2.update(cx, |pane, cx| {
23429        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23430        open_editor.update(cx, |editor, cx| {
23431            assert_eq!(
23432                editor.display_text(cx),
23433                main_text,
23434                "Original main.rs text on initial open in another panel",
23435            );
23436            assert_eq!(
23437                editor
23438                    .selections
23439                    .all::<Point>(cx)
23440                    .into_iter()
23441                    .map(|s| s.range())
23442                    .collect::<Vec<_>>(),
23443                vec![Point::zero()..Point::zero()],
23444                "Default selections on initial open in another panel",
23445            );
23446        })
23447    });
23448
23449    editor_2.update_in(cx, |editor, window, cx| {
23450        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23451    });
23452
23453    let _other_editor_1 = workspace
23454        .update_in(cx, |workspace, window, cx| {
23455            workspace.open_path(
23456                (worktree_id, "lib.rs"),
23457                Some(pane_1.downgrade()),
23458                true,
23459                window,
23460                cx,
23461            )
23462        })
23463        .unwrap()
23464        .await
23465        .downcast::<Editor>()
23466        .unwrap();
23467    pane_1
23468        .update_in(cx, |pane, window, cx| {
23469            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23470        })
23471        .await
23472        .unwrap();
23473    drop(editor_1);
23474    pane_1.update(cx, |pane, cx| {
23475        pane.active_item()
23476            .unwrap()
23477            .downcast::<Editor>()
23478            .unwrap()
23479            .update(cx, |editor, cx| {
23480                assert_eq!(
23481                    editor.display_text(cx),
23482                    lib_text,
23483                    "Other file should be open and active",
23484                );
23485            });
23486        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23487    });
23488
23489    let _other_editor_2 = workspace
23490        .update_in(cx, |workspace, window, cx| {
23491            workspace.open_path(
23492                (worktree_id, "lib.rs"),
23493                Some(pane_2.downgrade()),
23494                true,
23495                window,
23496                cx,
23497            )
23498        })
23499        .unwrap()
23500        .await
23501        .downcast::<Editor>()
23502        .unwrap();
23503    pane_2
23504        .update_in(cx, |pane, window, cx| {
23505            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23506        })
23507        .await
23508        .unwrap();
23509    drop(editor_2);
23510    pane_2.update(cx, |pane, cx| {
23511        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23512        open_editor.update(cx, |editor, cx| {
23513            assert_eq!(
23514                editor.display_text(cx),
23515                lib_text,
23516                "Other file should be open and active in another panel too",
23517            );
23518        });
23519        assert_eq!(
23520            pane.items().count(),
23521            1,
23522            "No other editors should be open in another pane",
23523        );
23524    });
23525
23526    let _editor_1_reopened = workspace
23527        .update_in(cx, |workspace, window, cx| {
23528            workspace.open_path(
23529                (worktree_id, "main.rs"),
23530                Some(pane_1.downgrade()),
23531                true,
23532                window,
23533                cx,
23534            )
23535        })
23536        .unwrap()
23537        .await
23538        .downcast::<Editor>()
23539        .unwrap();
23540    let _editor_2_reopened = workspace
23541        .update_in(cx, |workspace, window, cx| {
23542            workspace.open_path(
23543                (worktree_id, "main.rs"),
23544                Some(pane_2.downgrade()),
23545                true,
23546                window,
23547                cx,
23548            )
23549        })
23550        .unwrap()
23551        .await
23552        .downcast::<Editor>()
23553        .unwrap();
23554    pane_1.update(cx, |pane, cx| {
23555        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23556        open_editor.update(cx, |editor, cx| {
23557            assert_eq!(
23558                editor.display_text(cx),
23559                main_text,
23560                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23561            );
23562            assert_eq!(
23563                editor
23564                    .selections
23565                    .all::<Point>(cx)
23566                    .into_iter()
23567                    .map(|s| s.range())
23568                    .collect::<Vec<_>>(),
23569                expected_ranges,
23570                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23571            );
23572        })
23573    });
23574    pane_2.update(cx, |pane, cx| {
23575        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23576        open_editor.update(cx, |editor, cx| {
23577            assert_eq!(
23578                editor.display_text(cx),
23579                r#"fn main() {
23580⋯rintln!("1");
23581⋯intln!("2");
23582⋯ntln!("3");
23583println!("4");
23584println!("5");
23585}"#,
23586                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23587            );
23588            assert_eq!(
23589                editor
23590                    .selections
23591                    .all::<Point>(cx)
23592                    .into_iter()
23593                    .map(|s| s.range())
23594                    .collect::<Vec<_>>(),
23595                vec![Point::zero()..Point::zero()],
23596                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23597            );
23598        })
23599    });
23600}
23601
23602#[gpui::test]
23603async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23604    init_test(cx, |_| {});
23605
23606    let fs = FakeFs::new(cx.executor());
23607    let main_text = r#"fn main() {
23608println!("1");
23609println!("2");
23610println!("3");
23611println!("4");
23612println!("5");
23613}"#;
23614    let lib_text = "mod foo {}";
23615    fs.insert_tree(
23616        path!("/a"),
23617        json!({
23618            "lib.rs": lib_text,
23619            "main.rs": main_text,
23620        }),
23621    )
23622    .await;
23623
23624    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23625    let (workspace, cx) =
23626        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23627    let worktree_id = workspace.update(cx, |workspace, cx| {
23628        workspace.project().update(cx, |project, cx| {
23629            project.worktrees(cx).next().unwrap().read(cx).id()
23630        })
23631    });
23632
23633    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23634    let editor = workspace
23635        .update_in(cx, |workspace, window, cx| {
23636            workspace.open_path(
23637                (worktree_id, "main.rs"),
23638                Some(pane.downgrade()),
23639                true,
23640                window,
23641                cx,
23642            )
23643        })
23644        .unwrap()
23645        .await
23646        .downcast::<Editor>()
23647        .unwrap();
23648    pane.update(cx, |pane, cx| {
23649        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23650        open_editor.update(cx, |editor, cx| {
23651            assert_eq!(
23652                editor.display_text(cx),
23653                main_text,
23654                "Original main.rs text on initial open",
23655            );
23656        })
23657    });
23658    editor.update_in(cx, |editor, window, cx| {
23659        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23660    });
23661
23662    cx.update_global(|store: &mut SettingsStore, cx| {
23663        store.update_user_settings(cx, |s| {
23664            s.workspace.restore_on_file_reopen = Some(false);
23665        });
23666    });
23667    editor.update_in(cx, |editor, window, cx| {
23668        editor.fold_ranges(
23669            vec![
23670                Point::new(1, 0)..Point::new(1, 1),
23671                Point::new(2, 0)..Point::new(2, 2),
23672                Point::new(3, 0)..Point::new(3, 3),
23673            ],
23674            false,
23675            window,
23676            cx,
23677        );
23678    });
23679    pane.update_in(cx, |pane, window, cx| {
23680        pane.close_all_items(&CloseAllItems::default(), window, cx)
23681    })
23682    .await
23683    .unwrap();
23684    pane.update(cx, |pane, _| {
23685        assert!(pane.active_item().is_none());
23686    });
23687    cx.update_global(|store: &mut SettingsStore, cx| {
23688        store.update_user_settings(cx, |s| {
23689            s.workspace.restore_on_file_reopen = Some(true);
23690        });
23691    });
23692
23693    let _editor_reopened = workspace
23694        .update_in(cx, |workspace, window, cx| {
23695            workspace.open_path(
23696                (worktree_id, "main.rs"),
23697                Some(pane.downgrade()),
23698                true,
23699                window,
23700                cx,
23701            )
23702        })
23703        .unwrap()
23704        .await
23705        .downcast::<Editor>()
23706        .unwrap();
23707    pane.update(cx, |pane, cx| {
23708        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23709        open_editor.update(cx, |editor, cx| {
23710            assert_eq!(
23711                editor.display_text(cx),
23712                main_text,
23713                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23714            );
23715        })
23716    });
23717}
23718
23719#[gpui::test]
23720async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23721    struct EmptyModalView {
23722        focus_handle: gpui::FocusHandle,
23723    }
23724    impl EventEmitter<DismissEvent> for EmptyModalView {}
23725    impl Render for EmptyModalView {
23726        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23727            div()
23728        }
23729    }
23730    impl Focusable for EmptyModalView {
23731        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23732            self.focus_handle.clone()
23733        }
23734    }
23735    impl workspace::ModalView for EmptyModalView {}
23736    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23737        EmptyModalView {
23738            focus_handle: cx.focus_handle(),
23739        }
23740    }
23741
23742    init_test(cx, |_| {});
23743
23744    let fs = FakeFs::new(cx.executor());
23745    let project = Project::test(fs, [], cx).await;
23746    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23747    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23748    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23749    let editor = cx.new_window_entity(|window, cx| {
23750        Editor::new(
23751            EditorMode::full(),
23752            buffer,
23753            Some(project.clone()),
23754            window,
23755            cx,
23756        )
23757    });
23758    workspace
23759        .update(cx, |workspace, window, cx| {
23760            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23761        })
23762        .unwrap();
23763    editor.update_in(cx, |editor, window, cx| {
23764        editor.open_context_menu(&OpenContextMenu, window, cx);
23765        assert!(editor.mouse_context_menu.is_some());
23766    });
23767    workspace
23768        .update(cx, |workspace, window, cx| {
23769            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23770        })
23771        .unwrap();
23772    cx.read(|cx| {
23773        assert!(editor.read(cx).mouse_context_menu.is_none());
23774    });
23775}
23776
23777fn set_linked_edit_ranges(
23778    opening: (Point, Point),
23779    closing: (Point, Point),
23780    editor: &mut Editor,
23781    cx: &mut Context<Editor>,
23782) {
23783    let Some((buffer, _)) = editor
23784        .buffer
23785        .read(cx)
23786        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
23787    else {
23788        panic!("Failed to get buffer for selection position");
23789    };
23790    let buffer = buffer.read(cx);
23791    let buffer_id = buffer.remote_id();
23792    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
23793    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
23794    let mut linked_ranges = HashMap::default();
23795    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
23796    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
23797}
23798
23799#[gpui::test]
23800async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
23801    init_test(cx, |_| {});
23802
23803    let fs = FakeFs::new(cx.executor());
23804    fs.insert_file(path!("/file.html"), Default::default())
23805        .await;
23806
23807    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
23808
23809    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23810    let html_language = Arc::new(Language::new(
23811        LanguageConfig {
23812            name: "HTML".into(),
23813            matcher: LanguageMatcher {
23814                path_suffixes: vec!["html".to_string()],
23815                ..LanguageMatcher::default()
23816            },
23817            brackets: BracketPairConfig {
23818                pairs: vec![BracketPair {
23819                    start: "<".into(),
23820                    end: ">".into(),
23821                    close: true,
23822                    ..Default::default()
23823                }],
23824                ..Default::default()
23825            },
23826            ..Default::default()
23827        },
23828        Some(tree_sitter_html::LANGUAGE.into()),
23829    ));
23830    language_registry.add(html_language);
23831    let mut fake_servers = language_registry.register_fake_lsp(
23832        "HTML",
23833        FakeLspAdapter {
23834            capabilities: lsp::ServerCapabilities {
23835                completion_provider: Some(lsp::CompletionOptions {
23836                    resolve_provider: Some(true),
23837                    ..Default::default()
23838                }),
23839                ..Default::default()
23840            },
23841            ..Default::default()
23842        },
23843    );
23844
23845    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23846    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23847
23848    let worktree_id = workspace
23849        .update(cx, |workspace, _window, cx| {
23850            workspace.project().update(cx, |project, cx| {
23851                project.worktrees(cx).next().unwrap().read(cx).id()
23852            })
23853        })
23854        .unwrap();
23855    project
23856        .update(cx, |project, cx| {
23857            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
23858        })
23859        .await
23860        .unwrap();
23861    let editor = workspace
23862        .update(cx, |workspace, window, cx| {
23863            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
23864        })
23865        .unwrap()
23866        .await
23867        .unwrap()
23868        .downcast::<Editor>()
23869        .unwrap();
23870
23871    let fake_server = fake_servers.next().await.unwrap();
23872    editor.update_in(cx, |editor, window, cx| {
23873        editor.set_text("<ad></ad>", window, cx);
23874        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23875            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
23876        });
23877        set_linked_edit_ranges(
23878            (Point::new(0, 1), Point::new(0, 3)),
23879            (Point::new(0, 6), Point::new(0, 8)),
23880            editor,
23881            cx,
23882        );
23883    });
23884    let mut completion_handle =
23885        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
23886            Ok(Some(lsp::CompletionResponse::Array(vec![
23887                lsp::CompletionItem {
23888                    label: "head".to_string(),
23889                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23890                        lsp::InsertReplaceEdit {
23891                            new_text: "head".to_string(),
23892                            insert: lsp::Range::new(
23893                                lsp::Position::new(0, 1),
23894                                lsp::Position::new(0, 3),
23895                            ),
23896                            replace: lsp::Range::new(
23897                                lsp::Position::new(0, 1),
23898                                lsp::Position::new(0, 3),
23899                            ),
23900                        },
23901                    )),
23902                    ..Default::default()
23903                },
23904            ])))
23905        });
23906    editor.update_in(cx, |editor, window, cx| {
23907        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
23908    });
23909    cx.run_until_parked();
23910    completion_handle.next().await.unwrap();
23911    editor.update(cx, |editor, _| {
23912        assert!(
23913            editor.context_menu_visible(),
23914            "Completion menu should be visible"
23915        );
23916    });
23917    editor.update_in(cx, |editor, window, cx| {
23918        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
23919    });
23920    cx.executor().run_until_parked();
23921    editor.update(cx, |editor, cx| {
23922        assert_eq!(editor.text(cx), "<head></head>");
23923    });
23924}
23925
23926#[gpui::test]
23927async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
23928    init_test(cx, |_| {});
23929
23930    let mut cx = EditorTestContext::new(cx).await;
23931    let language = Arc::new(Language::new(
23932        LanguageConfig {
23933            name: "TSX".into(),
23934            matcher: LanguageMatcher {
23935                path_suffixes: vec!["tsx".to_string()],
23936                ..LanguageMatcher::default()
23937            },
23938            brackets: BracketPairConfig {
23939                pairs: vec![BracketPair {
23940                    start: "<".into(),
23941                    end: ">".into(),
23942                    close: true,
23943                    ..Default::default()
23944                }],
23945                ..Default::default()
23946            },
23947            linked_edit_characters: HashSet::from_iter(['.']),
23948            ..Default::default()
23949        },
23950        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
23951    ));
23952    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23953
23954    // Test typing > does not extend linked pair
23955    cx.set_state("<divˇ<div></div>");
23956    cx.update_editor(|editor, _, cx| {
23957        set_linked_edit_ranges(
23958            (Point::new(0, 1), Point::new(0, 4)),
23959            (Point::new(0, 11), Point::new(0, 14)),
23960            editor,
23961            cx,
23962        );
23963    });
23964    cx.update_editor(|editor, window, cx| {
23965        editor.handle_input(">", window, cx);
23966    });
23967    cx.assert_editor_state("<div>ˇ<div></div>");
23968
23969    // Test typing . do extend linked pair
23970    cx.set_state("<Animatedˇ></Animated>");
23971    cx.update_editor(|editor, _, cx| {
23972        set_linked_edit_ranges(
23973            (Point::new(0, 1), Point::new(0, 9)),
23974            (Point::new(0, 12), Point::new(0, 20)),
23975            editor,
23976            cx,
23977        );
23978    });
23979    cx.update_editor(|editor, window, cx| {
23980        editor.handle_input(".", window, cx);
23981    });
23982    cx.assert_editor_state("<Animated.ˇ></Animated.>");
23983    cx.update_editor(|editor, _, cx| {
23984        set_linked_edit_ranges(
23985            (Point::new(0, 1), Point::new(0, 10)),
23986            (Point::new(0, 13), Point::new(0, 21)),
23987            editor,
23988            cx,
23989        );
23990    });
23991    cx.update_editor(|editor, window, cx| {
23992        editor.handle_input("V", window, cx);
23993    });
23994    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
23995}
23996
23997#[gpui::test]
23998async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
23999    init_test(cx, |_| {});
24000
24001    let fs = FakeFs::new(cx.executor());
24002    fs.insert_tree(
24003        path!("/root"),
24004        json!({
24005            "a": {
24006                "main.rs": "fn main() {}",
24007            },
24008            "foo": {
24009                "bar": {
24010                    "external_file.rs": "pub mod external {}",
24011                }
24012            }
24013        }),
24014    )
24015    .await;
24016
24017    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24018    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24019    language_registry.add(rust_lang());
24020    let _fake_servers = language_registry.register_fake_lsp(
24021        "Rust",
24022        FakeLspAdapter {
24023            ..FakeLspAdapter::default()
24024        },
24025    );
24026    let (workspace, cx) =
24027        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24028    let worktree_id = workspace.update(cx, |workspace, cx| {
24029        workspace.project().update(cx, |project, cx| {
24030            project.worktrees(cx).next().unwrap().read(cx).id()
24031        })
24032    });
24033
24034    let assert_language_servers_count =
24035        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24036            project.update(cx, |project, cx| {
24037                let current = project
24038                    .lsp_store()
24039                    .read(cx)
24040                    .as_local()
24041                    .unwrap()
24042                    .language_servers
24043                    .len();
24044                assert_eq!(expected, current, "{context}");
24045            });
24046        };
24047
24048    assert_language_servers_count(
24049        0,
24050        "No servers should be running before any file is open",
24051        cx,
24052    );
24053    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24054    let main_editor = workspace
24055        .update_in(cx, |workspace, window, cx| {
24056            workspace.open_path(
24057                (worktree_id, "main.rs"),
24058                Some(pane.downgrade()),
24059                true,
24060                window,
24061                cx,
24062            )
24063        })
24064        .unwrap()
24065        .await
24066        .downcast::<Editor>()
24067        .unwrap();
24068    pane.update(cx, |pane, cx| {
24069        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24070        open_editor.update(cx, |editor, cx| {
24071            assert_eq!(
24072                editor.display_text(cx),
24073                "fn main() {}",
24074                "Original main.rs text on initial open",
24075            );
24076        });
24077        assert_eq!(open_editor, main_editor);
24078    });
24079    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24080
24081    let external_editor = workspace
24082        .update_in(cx, |workspace, window, cx| {
24083            workspace.open_abs_path(
24084                PathBuf::from("/root/foo/bar/external_file.rs"),
24085                OpenOptions::default(),
24086                window,
24087                cx,
24088            )
24089        })
24090        .await
24091        .expect("opening external file")
24092        .downcast::<Editor>()
24093        .expect("downcasted external file's open element to editor");
24094    pane.update(cx, |pane, cx| {
24095        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24096        open_editor.update(cx, |editor, cx| {
24097            assert_eq!(
24098                editor.display_text(cx),
24099                "pub mod external {}",
24100                "External file is open now",
24101            );
24102        });
24103        assert_eq!(open_editor, external_editor);
24104    });
24105    assert_language_servers_count(
24106        1,
24107        "Second, external, *.rs file should join the existing server",
24108        cx,
24109    );
24110
24111    pane.update_in(cx, |pane, window, cx| {
24112        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24113    })
24114    .await
24115    .unwrap();
24116    pane.update_in(cx, |pane, window, cx| {
24117        pane.navigate_backward(&Default::default(), window, cx);
24118    });
24119    cx.run_until_parked();
24120    pane.update(cx, |pane, cx| {
24121        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24122        open_editor.update(cx, |editor, cx| {
24123            assert_eq!(
24124                editor.display_text(cx),
24125                "pub mod external {}",
24126                "External file is open now",
24127            );
24128        });
24129    });
24130    assert_language_servers_count(
24131        1,
24132        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24133        cx,
24134    );
24135
24136    cx.update(|_, cx| {
24137        workspace::reload(cx);
24138    });
24139    assert_language_servers_count(
24140        1,
24141        "After reloading the worktree with local and external files opened, only one project should be started",
24142        cx,
24143    );
24144}
24145
24146#[gpui::test]
24147async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24148    init_test(cx, |_| {});
24149
24150    let mut cx = EditorTestContext::new(cx).await;
24151    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24152    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24153
24154    // test cursor move to start of each line on tab
24155    // for `if`, `elif`, `else`, `while`, `with` and `for`
24156    cx.set_state(indoc! {"
24157        def main():
24158        ˇ    for item in items:
24159        ˇ        while item.active:
24160        ˇ            if item.value > 10:
24161        ˇ                continue
24162        ˇ            elif item.value < 0:
24163        ˇ                break
24164        ˇ            else:
24165        ˇ                with item.context() as ctx:
24166        ˇ                    yield count
24167        ˇ        else:
24168        ˇ            log('while else')
24169        ˇ    else:
24170        ˇ        log('for else')
24171    "});
24172    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24173    cx.assert_editor_state(indoc! {"
24174        def main():
24175            ˇfor item in items:
24176                ˇwhile item.active:
24177                    ˇif item.value > 10:
24178                        ˇcontinue
24179                    ˇelif item.value < 0:
24180                        ˇbreak
24181                    ˇelse:
24182                        ˇwith item.context() as ctx:
24183                            ˇyield count
24184                ˇelse:
24185                    ˇlog('while else')
24186            ˇelse:
24187                ˇlog('for else')
24188    "});
24189    // test relative indent is preserved when tab
24190    // for `if`, `elif`, `else`, `while`, `with` and `for`
24191    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24192    cx.assert_editor_state(indoc! {"
24193        def main():
24194                ˇfor item in items:
24195                    ˇwhile item.active:
24196                        ˇif item.value > 10:
24197                            ˇcontinue
24198                        ˇelif item.value < 0:
24199                            ˇbreak
24200                        ˇelse:
24201                            ˇwith item.context() as ctx:
24202                                ˇyield count
24203                    ˇelse:
24204                        ˇlog('while else')
24205                ˇelse:
24206                    ˇlog('for else')
24207    "});
24208
24209    // test cursor move to start of each line on tab
24210    // for `try`, `except`, `else`, `finally`, `match` and `def`
24211    cx.set_state(indoc! {"
24212        def main():
24213        ˇ    try:
24214        ˇ        fetch()
24215        ˇ    except ValueError:
24216        ˇ        handle_error()
24217        ˇ    else:
24218        ˇ        match value:
24219        ˇ            case _:
24220        ˇ    finally:
24221        ˇ        def status():
24222        ˇ            return 0
24223    "});
24224    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24225    cx.assert_editor_state(indoc! {"
24226        def main():
24227            ˇtry:
24228                ˇfetch()
24229            ˇexcept ValueError:
24230                ˇhandle_error()
24231            ˇelse:
24232                ˇmatch value:
24233                    ˇcase _:
24234            ˇfinally:
24235                ˇdef status():
24236                    ˇreturn 0
24237    "});
24238    // test relative indent is preserved when tab
24239    // for `try`, `except`, `else`, `finally`, `match` and `def`
24240    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24241    cx.assert_editor_state(indoc! {"
24242        def main():
24243                ˇtry:
24244                    ˇfetch()
24245                ˇexcept ValueError:
24246                    ˇhandle_error()
24247                ˇelse:
24248                    ˇmatch value:
24249                        ˇcase _:
24250                ˇfinally:
24251                    ˇdef status():
24252                        ˇreturn 0
24253    "});
24254}
24255
24256#[gpui::test]
24257async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24258    init_test(cx, |_| {});
24259
24260    let mut cx = EditorTestContext::new(cx).await;
24261    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24262    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24263
24264    // test `else` auto outdents when typed inside `if` block
24265    cx.set_state(indoc! {"
24266        def main():
24267            if i == 2:
24268                return
24269                ˇ
24270    "});
24271    cx.update_editor(|editor, window, cx| {
24272        editor.handle_input("else:", window, cx);
24273    });
24274    cx.assert_editor_state(indoc! {"
24275        def main():
24276            if i == 2:
24277                return
24278            else:ˇ
24279    "});
24280
24281    // test `except` auto outdents when typed inside `try` block
24282    cx.set_state(indoc! {"
24283        def main():
24284            try:
24285                i = 2
24286                ˇ
24287    "});
24288    cx.update_editor(|editor, window, cx| {
24289        editor.handle_input("except:", window, cx);
24290    });
24291    cx.assert_editor_state(indoc! {"
24292        def main():
24293            try:
24294                i = 2
24295            except:ˇ
24296    "});
24297
24298    // test `else` auto outdents when typed inside `except` block
24299    cx.set_state(indoc! {"
24300        def main():
24301            try:
24302                i = 2
24303            except:
24304                j = 2
24305                ˇ
24306    "});
24307    cx.update_editor(|editor, window, cx| {
24308        editor.handle_input("else:", window, cx);
24309    });
24310    cx.assert_editor_state(indoc! {"
24311        def main():
24312            try:
24313                i = 2
24314            except:
24315                j = 2
24316            else:ˇ
24317    "});
24318
24319    // test `finally` auto outdents when typed inside `else` block
24320    cx.set_state(indoc! {"
24321        def main():
24322            try:
24323                i = 2
24324            except:
24325                j = 2
24326            else:
24327                k = 2
24328                ˇ
24329    "});
24330    cx.update_editor(|editor, window, cx| {
24331        editor.handle_input("finally:", window, cx);
24332    });
24333    cx.assert_editor_state(indoc! {"
24334        def main():
24335            try:
24336                i = 2
24337            except:
24338                j = 2
24339            else:
24340                k = 2
24341            finally:ˇ
24342    "});
24343
24344    // test `else` does not outdents when typed inside `except` block right after for block
24345    cx.set_state(indoc! {"
24346        def main():
24347            try:
24348                i = 2
24349            except:
24350                for i in range(n):
24351                    pass
24352                ˇ
24353    "});
24354    cx.update_editor(|editor, window, cx| {
24355        editor.handle_input("else:", window, cx);
24356    });
24357    cx.assert_editor_state(indoc! {"
24358        def main():
24359            try:
24360                i = 2
24361            except:
24362                for i in range(n):
24363                    pass
24364                else:ˇ
24365    "});
24366
24367    // test `finally` auto outdents when typed inside `else` block right after for block
24368    cx.set_state(indoc! {"
24369        def main():
24370            try:
24371                i = 2
24372            except:
24373                j = 2
24374            else:
24375                for i in range(n):
24376                    pass
24377                ˇ
24378    "});
24379    cx.update_editor(|editor, window, cx| {
24380        editor.handle_input("finally:", window, cx);
24381    });
24382    cx.assert_editor_state(indoc! {"
24383        def main():
24384            try:
24385                i = 2
24386            except:
24387                j = 2
24388            else:
24389                for i in range(n):
24390                    pass
24391            finally:ˇ
24392    "});
24393
24394    // test `except` outdents to inner "try" block
24395    cx.set_state(indoc! {"
24396        def main():
24397            try:
24398                i = 2
24399                if i == 2:
24400                    try:
24401                        i = 3
24402                        ˇ
24403    "});
24404    cx.update_editor(|editor, window, cx| {
24405        editor.handle_input("except:", window, cx);
24406    });
24407    cx.assert_editor_state(indoc! {"
24408        def main():
24409            try:
24410                i = 2
24411                if i == 2:
24412                    try:
24413                        i = 3
24414                    except:ˇ
24415    "});
24416
24417    // test `except` outdents to outer "try" block
24418    cx.set_state(indoc! {"
24419        def main():
24420            try:
24421                i = 2
24422                if i == 2:
24423                    try:
24424                        i = 3
24425                ˇ
24426    "});
24427    cx.update_editor(|editor, window, cx| {
24428        editor.handle_input("except:", window, cx);
24429    });
24430    cx.assert_editor_state(indoc! {"
24431        def main():
24432            try:
24433                i = 2
24434                if i == 2:
24435                    try:
24436                        i = 3
24437            except:ˇ
24438    "});
24439
24440    // test `else` stays at correct indent when typed after `for` block
24441    cx.set_state(indoc! {"
24442        def main():
24443            for i in range(10):
24444                if i == 3:
24445                    break
24446            ˇ
24447    "});
24448    cx.update_editor(|editor, window, cx| {
24449        editor.handle_input("else:", window, cx);
24450    });
24451    cx.assert_editor_state(indoc! {"
24452        def main():
24453            for i in range(10):
24454                if i == 3:
24455                    break
24456            else:ˇ
24457    "});
24458
24459    // test does not outdent on typing after line with square brackets
24460    cx.set_state(indoc! {"
24461        def f() -> list[str]:
24462            ˇ
24463    "});
24464    cx.update_editor(|editor, window, cx| {
24465        editor.handle_input("a", window, cx);
24466    });
24467    cx.assert_editor_state(indoc! {"
24468        def f() -> list[str]:
2446924470    "});
24471
24472    // test does not outdent on typing : after case keyword
24473    cx.set_state(indoc! {"
24474        match 1:
24475            caseˇ
24476    "});
24477    cx.update_editor(|editor, window, cx| {
24478        editor.handle_input(":", window, cx);
24479    });
24480    cx.assert_editor_state(indoc! {"
24481        match 1:
24482            case:ˇ
24483    "});
24484}
24485
24486#[gpui::test]
24487async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24488    init_test(cx, |_| {});
24489    update_test_language_settings(cx, |settings| {
24490        settings.defaults.extend_comment_on_newline = Some(false);
24491    });
24492    let mut cx = EditorTestContext::new(cx).await;
24493    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24494    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24495
24496    // test correct indent after newline on comment
24497    cx.set_state(indoc! {"
24498        # COMMENT:ˇ
24499    "});
24500    cx.update_editor(|editor, window, cx| {
24501        editor.newline(&Newline, window, cx);
24502    });
24503    cx.assert_editor_state(indoc! {"
24504        # COMMENT:
24505        ˇ
24506    "});
24507
24508    // test correct indent after newline in brackets
24509    cx.set_state(indoc! {"
24510        {ˇ}
24511    "});
24512    cx.update_editor(|editor, window, cx| {
24513        editor.newline(&Newline, window, cx);
24514    });
24515    cx.run_until_parked();
24516    cx.assert_editor_state(indoc! {"
24517        {
24518            ˇ
24519        }
24520    "});
24521
24522    cx.set_state(indoc! {"
24523        (ˇ)
24524    "});
24525    cx.update_editor(|editor, window, cx| {
24526        editor.newline(&Newline, window, cx);
24527    });
24528    cx.run_until_parked();
24529    cx.assert_editor_state(indoc! {"
24530        (
24531            ˇ
24532        )
24533    "});
24534
24535    // do not indent after empty lists or dictionaries
24536    cx.set_state(indoc! {"
24537        a = []ˇ
24538    "});
24539    cx.update_editor(|editor, window, cx| {
24540        editor.newline(&Newline, window, cx);
24541    });
24542    cx.run_until_parked();
24543    cx.assert_editor_state(indoc! {"
24544        a = []
24545        ˇ
24546    "});
24547}
24548
24549#[gpui::test]
24550async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24551    init_test(cx, |_| {});
24552
24553    let mut cx = EditorTestContext::new(cx).await;
24554    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24555    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24556
24557    // test cursor move to start of each line on tab
24558    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24559    cx.set_state(indoc! {"
24560        function main() {
24561        ˇ    for item in $items; do
24562        ˇ        while [ -n \"$item\" ]; do
24563        ˇ            if [ \"$value\" -gt 10 ]; then
24564        ˇ                continue
24565        ˇ            elif [ \"$value\" -lt 0 ]; then
24566        ˇ                break
24567        ˇ            else
24568        ˇ                echo \"$item\"
24569        ˇ            fi
24570        ˇ        done
24571        ˇ    done
24572        ˇ}
24573    "});
24574    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24575    cx.assert_editor_state(indoc! {"
24576        function main() {
24577            ˇfor item in $items; do
24578                ˇwhile [ -n \"$item\" ]; do
24579                    ˇif [ \"$value\" -gt 10 ]; then
24580                        ˇcontinue
24581                    ˇelif [ \"$value\" -lt 0 ]; then
24582                        ˇbreak
24583                    ˇelse
24584                        ˇecho \"$item\"
24585                    ˇfi
24586                ˇdone
24587            ˇdone
24588        ˇ}
24589    "});
24590    // test relative indent is preserved when tab
24591    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24592    cx.assert_editor_state(indoc! {"
24593        function main() {
24594                ˇfor item in $items; do
24595                    ˇwhile [ -n \"$item\" ]; do
24596                        ˇif [ \"$value\" -gt 10 ]; then
24597                            ˇcontinue
24598                        ˇelif [ \"$value\" -lt 0 ]; then
24599                            ˇbreak
24600                        ˇelse
24601                            ˇecho \"$item\"
24602                        ˇfi
24603                    ˇdone
24604                ˇdone
24605            ˇ}
24606    "});
24607
24608    // test cursor move to start of each line on tab
24609    // for `case` statement with patterns
24610    cx.set_state(indoc! {"
24611        function handle() {
24612        ˇ    case \"$1\" in
24613        ˇ        start)
24614        ˇ            echo \"a\"
24615        ˇ            ;;
24616        ˇ        stop)
24617        ˇ            echo \"b\"
24618        ˇ            ;;
24619        ˇ        *)
24620        ˇ            echo \"c\"
24621        ˇ            ;;
24622        ˇ    esac
24623        ˇ}
24624    "});
24625    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24626    cx.assert_editor_state(indoc! {"
24627        function handle() {
24628            ˇcase \"$1\" in
24629                ˇstart)
24630                    ˇecho \"a\"
24631                    ˇ;;
24632                ˇstop)
24633                    ˇecho \"b\"
24634                    ˇ;;
24635                ˇ*)
24636                    ˇecho \"c\"
24637                    ˇ;;
24638            ˇesac
24639        ˇ}
24640    "});
24641}
24642
24643#[gpui::test]
24644async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24645    init_test(cx, |_| {});
24646
24647    let mut cx = EditorTestContext::new(cx).await;
24648    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24649    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24650
24651    // test indents on comment insert
24652    cx.set_state(indoc! {"
24653        function main() {
24654        ˇ    for item in $items; do
24655        ˇ        while [ -n \"$item\" ]; do
24656        ˇ            if [ \"$value\" -gt 10 ]; then
24657        ˇ                continue
24658        ˇ            elif [ \"$value\" -lt 0 ]; then
24659        ˇ                break
24660        ˇ            else
24661        ˇ                echo \"$item\"
24662        ˇ            fi
24663        ˇ        done
24664        ˇ    done
24665        ˇ}
24666    "});
24667    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24668    cx.assert_editor_state(indoc! {"
24669        function main() {
24670        #ˇ    for item in $items; do
24671        #ˇ        while [ -n \"$item\" ]; do
24672        #ˇ            if [ \"$value\" -gt 10 ]; then
24673        #ˇ                continue
24674        #ˇ            elif [ \"$value\" -lt 0 ]; then
24675        #ˇ                break
24676        #ˇ            else
24677        #ˇ                echo \"$item\"
24678        #ˇ            fi
24679        #ˇ        done
24680        #ˇ    done
24681        #ˇ}
24682    "});
24683}
24684
24685#[gpui::test]
24686async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24687    init_test(cx, |_| {});
24688
24689    let mut cx = EditorTestContext::new(cx).await;
24690    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24691    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24692
24693    // test `else` auto outdents when typed inside `if` block
24694    cx.set_state(indoc! {"
24695        if [ \"$1\" = \"test\" ]; then
24696            echo \"foo bar\"
24697            ˇ
24698    "});
24699    cx.update_editor(|editor, window, cx| {
24700        editor.handle_input("else", window, cx);
24701    });
24702    cx.assert_editor_state(indoc! {"
24703        if [ \"$1\" = \"test\" ]; then
24704            echo \"foo bar\"
24705        elseˇ
24706    "});
24707
24708    // test `elif` auto outdents when typed inside `if` block
24709    cx.set_state(indoc! {"
24710        if [ \"$1\" = \"test\" ]; then
24711            echo \"foo bar\"
24712            ˇ
24713    "});
24714    cx.update_editor(|editor, window, cx| {
24715        editor.handle_input("elif", window, cx);
24716    });
24717    cx.assert_editor_state(indoc! {"
24718        if [ \"$1\" = \"test\" ]; then
24719            echo \"foo bar\"
24720        elifˇ
24721    "});
24722
24723    // test `fi` auto outdents when typed inside `else` block
24724    cx.set_state(indoc! {"
24725        if [ \"$1\" = \"test\" ]; then
24726            echo \"foo bar\"
24727        else
24728            echo \"bar baz\"
24729            ˇ
24730    "});
24731    cx.update_editor(|editor, window, cx| {
24732        editor.handle_input("fi", window, cx);
24733    });
24734    cx.assert_editor_state(indoc! {"
24735        if [ \"$1\" = \"test\" ]; then
24736            echo \"foo bar\"
24737        else
24738            echo \"bar baz\"
24739        fiˇ
24740    "});
24741
24742    // test `done` auto outdents when typed inside `while` block
24743    cx.set_state(indoc! {"
24744        while read line; do
24745            echo \"$line\"
24746            ˇ
24747    "});
24748    cx.update_editor(|editor, window, cx| {
24749        editor.handle_input("done", window, cx);
24750    });
24751    cx.assert_editor_state(indoc! {"
24752        while read line; do
24753            echo \"$line\"
24754        doneˇ
24755    "});
24756
24757    // test `done` auto outdents when typed inside `for` block
24758    cx.set_state(indoc! {"
24759        for file in *.txt; do
24760            cat \"$file\"
24761            ˇ
24762    "});
24763    cx.update_editor(|editor, window, cx| {
24764        editor.handle_input("done", window, cx);
24765    });
24766    cx.assert_editor_state(indoc! {"
24767        for file in *.txt; do
24768            cat \"$file\"
24769        doneˇ
24770    "});
24771
24772    // test `esac` auto outdents when typed inside `case` block
24773    cx.set_state(indoc! {"
24774        case \"$1\" in
24775            start)
24776                echo \"foo bar\"
24777                ;;
24778            stop)
24779                echo \"bar baz\"
24780                ;;
24781            ˇ
24782    "});
24783    cx.update_editor(|editor, window, cx| {
24784        editor.handle_input("esac", window, cx);
24785    });
24786    cx.assert_editor_state(indoc! {"
24787        case \"$1\" in
24788            start)
24789                echo \"foo bar\"
24790                ;;
24791            stop)
24792                echo \"bar baz\"
24793                ;;
24794        esacˇ
24795    "});
24796
24797    // test `*)` auto outdents when typed inside `case` block
24798    cx.set_state(indoc! {"
24799        case \"$1\" in
24800            start)
24801                echo \"foo bar\"
24802                ;;
24803                ˇ
24804    "});
24805    cx.update_editor(|editor, window, cx| {
24806        editor.handle_input("*)", window, cx);
24807    });
24808    cx.assert_editor_state(indoc! {"
24809        case \"$1\" in
24810            start)
24811                echo \"foo bar\"
24812                ;;
24813            *)ˇ
24814    "});
24815
24816    // test `fi` outdents to correct level with nested if blocks
24817    cx.set_state(indoc! {"
24818        if [ \"$1\" = \"test\" ]; then
24819            echo \"outer if\"
24820            if [ \"$2\" = \"debug\" ]; then
24821                echo \"inner if\"
24822                ˇ
24823    "});
24824    cx.update_editor(|editor, window, cx| {
24825        editor.handle_input("fi", window, cx);
24826    });
24827    cx.assert_editor_state(indoc! {"
24828        if [ \"$1\" = \"test\" ]; then
24829            echo \"outer if\"
24830            if [ \"$2\" = \"debug\" ]; then
24831                echo \"inner if\"
24832            fiˇ
24833    "});
24834}
24835
24836#[gpui::test]
24837async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
24838    init_test(cx, |_| {});
24839    update_test_language_settings(cx, |settings| {
24840        settings.defaults.extend_comment_on_newline = Some(false);
24841    });
24842    let mut cx = EditorTestContext::new(cx).await;
24843    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24844    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24845
24846    // test correct indent after newline on comment
24847    cx.set_state(indoc! {"
24848        # COMMENT:ˇ
24849    "});
24850    cx.update_editor(|editor, window, cx| {
24851        editor.newline(&Newline, window, cx);
24852    });
24853    cx.assert_editor_state(indoc! {"
24854        # COMMENT:
24855        ˇ
24856    "});
24857
24858    // test correct indent after newline after `then`
24859    cx.set_state(indoc! {"
24860
24861        if [ \"$1\" = \"test\" ]; thenˇ
24862    "});
24863    cx.update_editor(|editor, window, cx| {
24864        editor.newline(&Newline, window, cx);
24865    });
24866    cx.run_until_parked();
24867    cx.assert_editor_state(indoc! {"
24868
24869        if [ \"$1\" = \"test\" ]; then
24870            ˇ
24871    "});
24872
24873    // test correct indent after newline after `else`
24874    cx.set_state(indoc! {"
24875        if [ \"$1\" = \"test\" ]; then
24876        elseˇ
24877    "});
24878    cx.update_editor(|editor, window, cx| {
24879        editor.newline(&Newline, window, cx);
24880    });
24881    cx.run_until_parked();
24882    cx.assert_editor_state(indoc! {"
24883        if [ \"$1\" = \"test\" ]; then
24884        else
24885            ˇ
24886    "});
24887
24888    // test correct indent after newline after `elif`
24889    cx.set_state(indoc! {"
24890        if [ \"$1\" = \"test\" ]; then
24891        elifˇ
24892    "});
24893    cx.update_editor(|editor, window, cx| {
24894        editor.newline(&Newline, window, cx);
24895    });
24896    cx.run_until_parked();
24897    cx.assert_editor_state(indoc! {"
24898        if [ \"$1\" = \"test\" ]; then
24899        elif
24900            ˇ
24901    "});
24902
24903    // test correct indent after newline after `do`
24904    cx.set_state(indoc! {"
24905        for file in *.txt; doˇ
24906    "});
24907    cx.update_editor(|editor, window, cx| {
24908        editor.newline(&Newline, window, cx);
24909    });
24910    cx.run_until_parked();
24911    cx.assert_editor_state(indoc! {"
24912        for file in *.txt; do
24913            ˇ
24914    "});
24915
24916    // test correct indent after newline after case pattern
24917    cx.set_state(indoc! {"
24918        case \"$1\" in
24919            start)ˇ
24920    "});
24921    cx.update_editor(|editor, window, cx| {
24922        editor.newline(&Newline, window, cx);
24923    });
24924    cx.run_until_parked();
24925    cx.assert_editor_state(indoc! {"
24926        case \"$1\" in
24927            start)
24928                ˇ
24929    "});
24930
24931    // test correct indent after newline after case pattern
24932    cx.set_state(indoc! {"
24933        case \"$1\" in
24934            start)
24935                ;;
24936            *)ˇ
24937    "});
24938    cx.update_editor(|editor, window, cx| {
24939        editor.newline(&Newline, window, cx);
24940    });
24941    cx.run_until_parked();
24942    cx.assert_editor_state(indoc! {"
24943        case \"$1\" in
24944            start)
24945                ;;
24946            *)
24947                ˇ
24948    "});
24949
24950    // test correct indent after newline after function opening brace
24951    cx.set_state(indoc! {"
24952        function test() {ˇ}
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        function test() {
24960            ˇ
24961        }
24962    "});
24963
24964    // test no extra indent after semicolon on same line
24965    cx.set_state(indoc! {"
24966        echo \"test\"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        echo \"test\";
24974        ˇ
24975    "});
24976}
24977
24978fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
24979    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
24980    point..point
24981}
24982
24983#[track_caller]
24984fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
24985    let (text, ranges) = marked_text_ranges(marked_text, true);
24986    assert_eq!(editor.text(cx), text);
24987    assert_eq!(
24988        editor.selections.ranges(cx),
24989        ranges,
24990        "Assert selections are {}",
24991        marked_text
24992    );
24993}
24994
24995pub fn handle_signature_help_request(
24996    cx: &mut EditorLspTestContext,
24997    mocked_response: lsp::SignatureHelp,
24998) -> impl Future<Output = ()> + use<> {
24999    let mut request =
25000        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25001            let mocked_response = mocked_response.clone();
25002            async move { Ok(Some(mocked_response)) }
25003        });
25004
25005    async move {
25006        request.next().await;
25007    }
25008}
25009
25010#[track_caller]
25011pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25012    cx.update_editor(|editor, _, _| {
25013        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25014            let entries = menu.entries.borrow();
25015            let entries = entries
25016                .iter()
25017                .map(|entry| entry.string.as_str())
25018                .collect::<Vec<_>>();
25019            assert_eq!(entries, expected);
25020        } else {
25021            panic!("Expected completions menu");
25022        }
25023    });
25024}
25025
25026/// Handle completion request passing a marked string specifying where the completion
25027/// should be triggered from using '|' character, what range should be replaced, and what completions
25028/// should be returned using '<' and '>' to delimit the range.
25029///
25030/// Also see `handle_completion_request_with_insert_and_replace`.
25031#[track_caller]
25032pub fn handle_completion_request(
25033    marked_string: &str,
25034    completions: Vec<&'static str>,
25035    is_incomplete: bool,
25036    counter: Arc<AtomicUsize>,
25037    cx: &mut EditorLspTestContext,
25038) -> impl Future<Output = ()> {
25039    let complete_from_marker: TextRangeMarker = '|'.into();
25040    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25041    let (_, mut marked_ranges) = marked_text_ranges_by(
25042        marked_string,
25043        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25044    );
25045
25046    let complete_from_position =
25047        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25048    let replace_range =
25049        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25050
25051    let mut request =
25052        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25053            let completions = completions.clone();
25054            counter.fetch_add(1, atomic::Ordering::Release);
25055            async move {
25056                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25057                assert_eq!(
25058                    params.text_document_position.position,
25059                    complete_from_position
25060                );
25061                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25062                    is_incomplete,
25063                    item_defaults: None,
25064                    items: completions
25065                        .iter()
25066                        .map(|completion_text| lsp::CompletionItem {
25067                            label: completion_text.to_string(),
25068                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25069                                range: replace_range,
25070                                new_text: completion_text.to_string(),
25071                            })),
25072                            ..Default::default()
25073                        })
25074                        .collect(),
25075                })))
25076            }
25077        });
25078
25079    async move {
25080        request.next().await;
25081    }
25082}
25083
25084/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25085/// given instead, which also contains an `insert` range.
25086///
25087/// This function uses markers to define ranges:
25088/// - `|` marks the cursor position
25089/// - `<>` marks the replace range
25090/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25091pub fn handle_completion_request_with_insert_and_replace(
25092    cx: &mut EditorLspTestContext,
25093    marked_string: &str,
25094    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25095    counter: Arc<AtomicUsize>,
25096) -> impl Future<Output = ()> {
25097    let complete_from_marker: TextRangeMarker = '|'.into();
25098    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25099    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25100
25101    let (_, mut marked_ranges) = marked_text_ranges_by(
25102        marked_string,
25103        vec![
25104            complete_from_marker.clone(),
25105            replace_range_marker.clone(),
25106            insert_range_marker.clone(),
25107        ],
25108    );
25109
25110    let complete_from_position =
25111        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25112    let replace_range =
25113        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25114
25115    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25116        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25117        _ => lsp::Range {
25118            start: replace_range.start,
25119            end: complete_from_position,
25120        },
25121    };
25122
25123    let mut request =
25124        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25125            let completions = completions.clone();
25126            counter.fetch_add(1, atomic::Ordering::Release);
25127            async move {
25128                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25129                assert_eq!(
25130                    params.text_document_position.position, complete_from_position,
25131                    "marker `|` position doesn't match",
25132                );
25133                Ok(Some(lsp::CompletionResponse::Array(
25134                    completions
25135                        .iter()
25136                        .map(|(label, new_text)| lsp::CompletionItem {
25137                            label: label.to_string(),
25138                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25139                                lsp::InsertReplaceEdit {
25140                                    insert: insert_range,
25141                                    replace: replace_range,
25142                                    new_text: new_text.to_string(),
25143                                },
25144                            )),
25145                            ..Default::default()
25146                        })
25147                        .collect(),
25148                )))
25149            }
25150        });
25151
25152    async move {
25153        request.next().await;
25154    }
25155}
25156
25157fn handle_resolve_completion_request(
25158    cx: &mut EditorLspTestContext,
25159    edits: Option<Vec<(&'static str, &'static str)>>,
25160) -> impl Future<Output = ()> {
25161    let edits = edits.map(|edits| {
25162        edits
25163            .iter()
25164            .map(|(marked_string, new_text)| {
25165                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25166                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25167                lsp::TextEdit::new(replace_range, new_text.to_string())
25168            })
25169            .collect::<Vec<_>>()
25170    });
25171
25172    let mut request =
25173        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25174            let edits = edits.clone();
25175            async move {
25176                Ok(lsp::CompletionItem {
25177                    additional_text_edits: edits,
25178                    ..Default::default()
25179                })
25180            }
25181        });
25182
25183    async move {
25184        request.next().await;
25185    }
25186}
25187
25188pub(crate) fn update_test_language_settings(
25189    cx: &mut TestAppContext,
25190    f: impl Fn(&mut AllLanguageSettingsContent),
25191) {
25192    cx.update(|cx| {
25193        SettingsStore::update_global(cx, |store, cx| {
25194            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25195        });
25196    });
25197}
25198
25199pub(crate) fn update_test_project_settings(
25200    cx: &mut TestAppContext,
25201    f: impl Fn(&mut ProjectSettingsContent),
25202) {
25203    cx.update(|cx| {
25204        SettingsStore::update_global(cx, |store, cx| {
25205            store.update_user_settings(cx, |settings| f(&mut settings.project));
25206        });
25207    });
25208}
25209
25210pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25211    cx.update(|cx| {
25212        assets::Assets.load_test_fonts(cx);
25213        let store = SettingsStore::test(cx);
25214        cx.set_global(store);
25215        theme::init(theme::LoadThemes::JustBase, cx);
25216        release_channel::init(SemanticVersion::default(), cx);
25217        client::init_settings(cx);
25218        language::init(cx);
25219        Project::init_settings(cx);
25220        workspace::init_settings(cx);
25221        crate::init(cx);
25222    });
25223    zlog::init_test();
25224    update_test_language_settings(cx, f);
25225}
25226
25227#[track_caller]
25228fn assert_hunk_revert(
25229    not_reverted_text_with_selections: &str,
25230    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25231    expected_reverted_text_with_selections: &str,
25232    base_text: &str,
25233    cx: &mut EditorLspTestContext,
25234) {
25235    cx.set_state(not_reverted_text_with_selections);
25236    cx.set_head_text(base_text);
25237    cx.executor().run_until_parked();
25238
25239    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25240        let snapshot = editor.snapshot(window, cx);
25241        let reverted_hunk_statuses = snapshot
25242            .buffer_snapshot
25243            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
25244            .map(|hunk| hunk.status().kind)
25245            .collect::<Vec<_>>();
25246
25247        editor.git_restore(&Default::default(), window, cx);
25248        reverted_hunk_statuses
25249    });
25250    cx.executor().run_until_parked();
25251    cx.assert_editor_state(expected_reverted_text_with_selections);
25252    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25253}
25254
25255#[gpui::test(iterations = 10)]
25256async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25257    init_test(cx, |_| {});
25258
25259    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25260    let counter = diagnostic_requests.clone();
25261
25262    let fs = FakeFs::new(cx.executor());
25263    fs.insert_tree(
25264        path!("/a"),
25265        json!({
25266            "first.rs": "fn main() { let a = 5; }",
25267            "second.rs": "// Test file",
25268        }),
25269    )
25270    .await;
25271
25272    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25273    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25274    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25275
25276    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25277    language_registry.add(rust_lang());
25278    let mut fake_servers = language_registry.register_fake_lsp(
25279        "Rust",
25280        FakeLspAdapter {
25281            capabilities: lsp::ServerCapabilities {
25282                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25283                    lsp::DiagnosticOptions {
25284                        identifier: None,
25285                        inter_file_dependencies: true,
25286                        workspace_diagnostics: true,
25287                        work_done_progress_options: Default::default(),
25288                    },
25289                )),
25290                ..Default::default()
25291            },
25292            ..Default::default()
25293        },
25294    );
25295
25296    let editor = workspace
25297        .update(cx, |workspace, window, cx| {
25298            workspace.open_abs_path(
25299                PathBuf::from(path!("/a/first.rs")),
25300                OpenOptions::default(),
25301                window,
25302                cx,
25303            )
25304        })
25305        .unwrap()
25306        .await
25307        .unwrap()
25308        .downcast::<Editor>()
25309        .unwrap();
25310    let fake_server = fake_servers.next().await.unwrap();
25311    let server_id = fake_server.server.server_id();
25312    let mut first_request = fake_server
25313        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25314            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25315            let result_id = Some(new_result_id.to_string());
25316            assert_eq!(
25317                params.text_document.uri,
25318                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25319            );
25320            async move {
25321                Ok(lsp::DocumentDiagnosticReportResult::Report(
25322                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25323                        related_documents: None,
25324                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25325                            items: Vec::new(),
25326                            result_id,
25327                        },
25328                    }),
25329                ))
25330            }
25331        });
25332
25333    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25334        project.update(cx, |project, cx| {
25335            let buffer_id = editor
25336                .read(cx)
25337                .buffer()
25338                .read(cx)
25339                .as_singleton()
25340                .expect("created a singleton buffer")
25341                .read(cx)
25342                .remote_id();
25343            let buffer_result_id = project
25344                .lsp_store()
25345                .read(cx)
25346                .result_id(server_id, buffer_id, cx);
25347            assert_eq!(expected, buffer_result_id);
25348        });
25349    };
25350
25351    ensure_result_id(None, cx);
25352    cx.executor().advance_clock(Duration::from_millis(60));
25353    cx.executor().run_until_parked();
25354    assert_eq!(
25355        diagnostic_requests.load(atomic::Ordering::Acquire),
25356        1,
25357        "Opening file should trigger diagnostic request"
25358    );
25359    first_request
25360        .next()
25361        .await
25362        .expect("should have sent the first diagnostics pull request");
25363    ensure_result_id(Some("1".to_string()), cx);
25364
25365    // Editing should trigger diagnostics
25366    editor.update_in(cx, |editor, window, cx| {
25367        editor.handle_input("2", window, cx)
25368    });
25369    cx.executor().advance_clock(Duration::from_millis(60));
25370    cx.executor().run_until_parked();
25371    assert_eq!(
25372        diagnostic_requests.load(atomic::Ordering::Acquire),
25373        2,
25374        "Editing should trigger diagnostic request"
25375    );
25376    ensure_result_id(Some("2".to_string()), cx);
25377
25378    // Moving cursor should not trigger diagnostic request
25379    editor.update_in(cx, |editor, window, cx| {
25380        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25381            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25382        });
25383    });
25384    cx.executor().advance_clock(Duration::from_millis(60));
25385    cx.executor().run_until_parked();
25386    assert_eq!(
25387        diagnostic_requests.load(atomic::Ordering::Acquire),
25388        2,
25389        "Cursor movement should not trigger diagnostic request"
25390    );
25391    ensure_result_id(Some("2".to_string()), cx);
25392    // Multiple rapid edits should be debounced
25393    for _ in 0..5 {
25394        editor.update_in(cx, |editor, window, cx| {
25395            editor.handle_input("x", window, cx)
25396        });
25397    }
25398    cx.executor().advance_clock(Duration::from_millis(60));
25399    cx.executor().run_until_parked();
25400
25401    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25402    assert!(
25403        final_requests <= 4,
25404        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25405    );
25406    ensure_result_id(Some(final_requests.to_string()), cx);
25407}
25408
25409#[gpui::test]
25410async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25411    // Regression test for issue #11671
25412    // Previously, adding a cursor after moving multiple cursors would reset
25413    // the cursor count instead of adding to the existing cursors.
25414    init_test(cx, |_| {});
25415    let mut cx = EditorTestContext::new(cx).await;
25416
25417    // Create a simple buffer with cursor at start
25418    cx.set_state(indoc! {"
25419        ˇaaaa
25420        bbbb
25421        cccc
25422        dddd
25423        eeee
25424        ffff
25425        gggg
25426        hhhh"});
25427
25428    // Add 2 cursors below (so we have 3 total)
25429    cx.update_editor(|editor, window, cx| {
25430        editor.add_selection_below(&Default::default(), window, cx);
25431        editor.add_selection_below(&Default::default(), window, cx);
25432    });
25433
25434    // Verify we have 3 cursors
25435    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25436    assert_eq!(
25437        initial_count, 3,
25438        "Should have 3 cursors after adding 2 below"
25439    );
25440
25441    // Move down one line
25442    cx.update_editor(|editor, window, cx| {
25443        editor.move_down(&MoveDown, window, cx);
25444    });
25445
25446    // Add another cursor below
25447    cx.update_editor(|editor, window, cx| {
25448        editor.add_selection_below(&Default::default(), window, cx);
25449    });
25450
25451    // Should now have 4 cursors (3 original + 1 new)
25452    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25453    assert_eq!(
25454        final_count, 4,
25455        "Should have 4 cursors after moving and adding another"
25456    );
25457}
25458
25459#[gpui::test(iterations = 10)]
25460async fn test_document_colors(cx: &mut TestAppContext) {
25461    let expected_color = Rgba {
25462        r: 0.33,
25463        g: 0.33,
25464        b: 0.33,
25465        a: 0.33,
25466    };
25467
25468    init_test(cx, |_| {});
25469
25470    let fs = FakeFs::new(cx.executor());
25471    fs.insert_tree(
25472        path!("/a"),
25473        json!({
25474            "first.rs": "fn main() { let a = 5; }",
25475        }),
25476    )
25477    .await;
25478
25479    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25480    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25481    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25482
25483    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25484    language_registry.add(rust_lang());
25485    let mut fake_servers = language_registry.register_fake_lsp(
25486        "Rust",
25487        FakeLspAdapter {
25488            capabilities: lsp::ServerCapabilities {
25489                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25490                ..lsp::ServerCapabilities::default()
25491            },
25492            name: "rust-analyzer",
25493            ..FakeLspAdapter::default()
25494        },
25495    );
25496    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25497        "Rust",
25498        FakeLspAdapter {
25499            capabilities: lsp::ServerCapabilities {
25500                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25501                ..lsp::ServerCapabilities::default()
25502            },
25503            name: "not-rust-analyzer",
25504            ..FakeLspAdapter::default()
25505        },
25506    );
25507
25508    let editor = workspace
25509        .update(cx, |workspace, window, cx| {
25510            workspace.open_abs_path(
25511                PathBuf::from(path!("/a/first.rs")),
25512                OpenOptions::default(),
25513                window,
25514                cx,
25515            )
25516        })
25517        .unwrap()
25518        .await
25519        .unwrap()
25520        .downcast::<Editor>()
25521        .unwrap();
25522    let fake_language_server = fake_servers.next().await.unwrap();
25523    let fake_language_server_without_capabilities =
25524        fake_servers_without_capabilities.next().await.unwrap();
25525    let requests_made = Arc::new(AtomicUsize::new(0));
25526    let closure_requests_made = Arc::clone(&requests_made);
25527    let mut color_request_handle = fake_language_server
25528        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25529            let requests_made = Arc::clone(&closure_requests_made);
25530            async move {
25531                assert_eq!(
25532                    params.text_document.uri,
25533                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25534                );
25535                requests_made.fetch_add(1, atomic::Ordering::Release);
25536                Ok(vec![
25537                    lsp::ColorInformation {
25538                        range: lsp::Range {
25539                            start: lsp::Position {
25540                                line: 0,
25541                                character: 0,
25542                            },
25543                            end: lsp::Position {
25544                                line: 0,
25545                                character: 1,
25546                            },
25547                        },
25548                        color: lsp::Color {
25549                            red: 0.33,
25550                            green: 0.33,
25551                            blue: 0.33,
25552                            alpha: 0.33,
25553                        },
25554                    },
25555                    lsp::ColorInformation {
25556                        range: lsp::Range {
25557                            start: lsp::Position {
25558                                line: 0,
25559                                character: 0,
25560                            },
25561                            end: lsp::Position {
25562                                line: 0,
25563                                character: 1,
25564                            },
25565                        },
25566                        color: lsp::Color {
25567                            red: 0.33,
25568                            green: 0.33,
25569                            blue: 0.33,
25570                            alpha: 0.33,
25571                        },
25572                    },
25573                ])
25574            }
25575        });
25576
25577    let _handle = fake_language_server_without_capabilities
25578        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25579            panic!("Should not be called");
25580        });
25581    cx.executor().advance_clock(Duration::from_millis(100));
25582    color_request_handle.next().await.unwrap();
25583    cx.run_until_parked();
25584    assert_eq!(
25585        1,
25586        requests_made.load(atomic::Ordering::Acquire),
25587        "Should query for colors once per editor open"
25588    );
25589    editor.update_in(cx, |editor, _, cx| {
25590        assert_eq!(
25591            vec![expected_color],
25592            extract_color_inlays(editor, cx),
25593            "Should have an initial inlay"
25594        );
25595    });
25596
25597    // opening another file in a split should not influence the LSP query counter
25598    workspace
25599        .update(cx, |workspace, window, cx| {
25600            assert_eq!(
25601                workspace.panes().len(),
25602                1,
25603                "Should have one pane with one editor"
25604            );
25605            workspace.move_item_to_pane_in_direction(
25606                &MoveItemToPaneInDirection {
25607                    direction: SplitDirection::Right,
25608                    focus: false,
25609                    clone: true,
25610                },
25611                window,
25612                cx,
25613            );
25614        })
25615        .unwrap();
25616    cx.run_until_parked();
25617    workspace
25618        .update(cx, |workspace, _, cx| {
25619            let panes = workspace.panes();
25620            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25621            for pane in panes {
25622                let editor = pane
25623                    .read(cx)
25624                    .active_item()
25625                    .and_then(|item| item.downcast::<Editor>())
25626                    .expect("Should have opened an editor in each split");
25627                let editor_file = editor
25628                    .read(cx)
25629                    .buffer()
25630                    .read(cx)
25631                    .as_singleton()
25632                    .expect("test deals with singleton buffers")
25633                    .read(cx)
25634                    .file()
25635                    .expect("test buffese should have a file")
25636                    .path();
25637                assert_eq!(
25638                    editor_file.as_ref(),
25639                    Path::new("first.rs"),
25640                    "Both editors should be opened for the same file"
25641                )
25642            }
25643        })
25644        .unwrap();
25645
25646    cx.executor().advance_clock(Duration::from_millis(500));
25647    let save = editor.update_in(cx, |editor, window, cx| {
25648        editor.move_to_end(&MoveToEnd, window, cx);
25649        editor.handle_input("dirty", window, cx);
25650        editor.save(
25651            SaveOptions {
25652                format: true,
25653                autosave: true,
25654            },
25655            project.clone(),
25656            window,
25657            cx,
25658        )
25659    });
25660    save.await.unwrap();
25661
25662    color_request_handle.next().await.unwrap();
25663    cx.run_until_parked();
25664    assert_eq!(
25665        3,
25666        requests_made.load(atomic::Ordering::Acquire),
25667        "Should query for colors once per save and once per formatting after save"
25668    );
25669
25670    drop(editor);
25671    let close = workspace
25672        .update(cx, |workspace, window, cx| {
25673            workspace.active_pane().update(cx, |pane, cx| {
25674                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25675            })
25676        })
25677        .unwrap();
25678    close.await.unwrap();
25679    let close = workspace
25680        .update(cx, |workspace, window, cx| {
25681            workspace.active_pane().update(cx, |pane, cx| {
25682                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25683            })
25684        })
25685        .unwrap();
25686    close.await.unwrap();
25687    assert_eq!(
25688        3,
25689        requests_made.load(atomic::Ordering::Acquire),
25690        "After saving and closing all editors, no extra requests should be made"
25691    );
25692    workspace
25693        .update(cx, |workspace, _, cx| {
25694            assert!(
25695                workspace.active_item(cx).is_none(),
25696                "Should close all editors"
25697            )
25698        })
25699        .unwrap();
25700
25701    workspace
25702        .update(cx, |workspace, window, cx| {
25703            workspace.active_pane().update(cx, |pane, cx| {
25704                pane.navigate_backward(&Default::default(), window, cx);
25705            })
25706        })
25707        .unwrap();
25708    cx.executor().advance_clock(Duration::from_millis(100));
25709    cx.run_until_parked();
25710    let editor = workspace
25711        .update(cx, |workspace, _, cx| {
25712            workspace
25713                .active_item(cx)
25714                .expect("Should have reopened the editor again after navigating back")
25715                .downcast::<Editor>()
25716                .expect("Should be an editor")
25717        })
25718        .unwrap();
25719    color_request_handle.next().await.unwrap();
25720    assert_eq!(
25721        3,
25722        requests_made.load(atomic::Ordering::Acquire),
25723        "Cache should be reused on buffer close and reopen"
25724    );
25725    editor.update(cx, |editor, cx| {
25726        assert_eq!(
25727            vec![expected_color],
25728            extract_color_inlays(editor, cx),
25729            "Should have an initial inlay"
25730        );
25731    });
25732}
25733
25734#[gpui::test]
25735async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
25736    init_test(cx, |_| {});
25737    let (editor, cx) = cx.add_window_view(Editor::single_line);
25738    editor.update_in(cx, |editor, window, cx| {
25739        editor.set_text("oops\n\nwow\n", window, cx)
25740    });
25741    cx.run_until_parked();
25742    editor.update(cx, |editor, cx| {
25743        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
25744    });
25745    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
25746    cx.run_until_parked();
25747    editor.update(cx, |editor, cx| {
25748        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
25749    });
25750}
25751
25752#[gpui::test]
25753async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
25754    init_test(cx, |_| {});
25755
25756    cx.update(|cx| {
25757        register_project_item::<Editor>(cx);
25758    });
25759
25760    let fs = FakeFs::new(cx.executor());
25761    fs.insert_tree("/root1", json!({})).await;
25762    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
25763        .await;
25764
25765    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
25766    let (workspace, cx) =
25767        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25768
25769    let worktree_id = project.update(cx, |project, cx| {
25770        project.worktrees(cx).next().unwrap().read(cx).id()
25771    });
25772
25773    let handle = workspace
25774        .update_in(cx, |workspace, window, cx| {
25775            let project_path = (worktree_id, "one.pdf");
25776            workspace.open_path(project_path, None, true, window, cx)
25777        })
25778        .await
25779        .unwrap();
25780
25781    assert_eq!(
25782        handle.to_any().entity_type(),
25783        TypeId::of::<InvalidBufferView>()
25784    );
25785}
25786
25787#[gpui::test]
25788async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
25789    init_test(cx, |_| {});
25790
25791    let language = Arc::new(Language::new(
25792        LanguageConfig::default(),
25793        Some(tree_sitter_rust::LANGUAGE.into()),
25794    ));
25795
25796    // Test hierarchical sibling navigation
25797    let text = r#"
25798        fn outer() {
25799            if condition {
25800                let a = 1;
25801            }
25802            let b = 2;
25803        }
25804
25805        fn another() {
25806            let c = 3;
25807        }
25808    "#;
25809
25810    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
25811    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25812    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
25813
25814    // Wait for parsing to complete
25815    editor
25816        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
25817        .await;
25818
25819    editor.update_in(cx, |editor, window, cx| {
25820        // Start by selecting "let a = 1;" inside the if block
25821        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25822            s.select_display_ranges([
25823                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
25824            ]);
25825        });
25826
25827        let initial_selection = editor.selections.display_ranges(cx);
25828        assert_eq!(initial_selection.len(), 1, "Should have one selection");
25829
25830        // Test select next sibling - should move up levels to find the next sibling
25831        // Since "let a = 1;" has no siblings in the if block, it should move up
25832        // to find "let b = 2;" which is a sibling of the if block
25833        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25834        let next_selection = editor.selections.display_ranges(cx);
25835
25836        // Should have a selection and it should be different from the initial
25837        assert_eq!(
25838            next_selection.len(),
25839            1,
25840            "Should have one selection after next"
25841        );
25842        assert_ne!(
25843            next_selection[0], initial_selection[0],
25844            "Next sibling selection should be different"
25845        );
25846
25847        // Test hierarchical navigation by going to the end of the current function
25848        // and trying to navigate to the next function
25849        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25850            s.select_display_ranges([
25851                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
25852            ]);
25853        });
25854
25855        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25856        let function_next_selection = editor.selections.display_ranges(cx);
25857
25858        // Should move to the next function
25859        assert_eq!(
25860            function_next_selection.len(),
25861            1,
25862            "Should have one selection after function next"
25863        );
25864
25865        // Test select previous sibling navigation
25866        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
25867        let prev_selection = editor.selections.display_ranges(cx);
25868
25869        // Should have a selection and it should be different
25870        assert_eq!(
25871            prev_selection.len(),
25872            1,
25873            "Should have one selection after prev"
25874        );
25875        assert_ne!(
25876            prev_selection[0], function_next_selection[0],
25877            "Previous sibling selection should be different from next"
25878        );
25879    });
25880}
25881
25882#[gpui::test]
25883async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
25884    init_test(cx, |_| {});
25885
25886    let mut cx = EditorTestContext::new(cx).await;
25887    cx.set_state(
25888        "let ˇvariable = 42;
25889let another = variable + 1;
25890let result = variable * 2;",
25891    );
25892
25893    // Set up document highlights manually (simulating LSP response)
25894    cx.update_editor(|editor, _window, cx| {
25895        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
25896
25897        // Create highlights for "variable" occurrences
25898        let highlight_ranges = [
25899            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
25900            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
25901            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
25902        ];
25903
25904        let anchor_ranges: Vec<_> = highlight_ranges
25905            .iter()
25906            .map(|range| range.clone().to_anchors(&buffer_snapshot))
25907            .collect();
25908
25909        editor.highlight_background::<DocumentHighlightRead>(
25910            &anchor_ranges,
25911            |theme| theme.colors().editor_document_highlight_read_background,
25912            cx,
25913        );
25914    });
25915
25916    // Go to next highlight - should move to second "variable"
25917    cx.update_editor(|editor, window, cx| {
25918        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25919    });
25920    cx.assert_editor_state(
25921        "let variable = 42;
25922let another = ˇvariable + 1;
25923let result = variable * 2;",
25924    );
25925
25926    // Go to next highlight - should move to third "variable"
25927    cx.update_editor(|editor, window, cx| {
25928        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25929    });
25930    cx.assert_editor_state(
25931        "let variable = 42;
25932let another = variable + 1;
25933let result = ˇvariable * 2;",
25934    );
25935
25936    // Go to next highlight - should stay at third "variable" (no wrap-around)
25937    cx.update_editor(|editor, window, cx| {
25938        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25939    });
25940    cx.assert_editor_state(
25941        "let variable = 42;
25942let another = variable + 1;
25943let result = ˇvariable * 2;",
25944    );
25945
25946    // Now test going backwards from third position
25947    cx.update_editor(|editor, window, cx| {
25948        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
25949    });
25950    cx.assert_editor_state(
25951        "let variable = 42;
25952let another = ˇvariable + 1;
25953let result = variable * 2;",
25954    );
25955
25956    // Go to previous highlight - should move to first "variable"
25957    cx.update_editor(|editor, window, cx| {
25958        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
25959    });
25960    cx.assert_editor_state(
25961        "let ˇvariable = 42;
25962let another = variable + 1;
25963let result = variable * 2;",
25964    );
25965
25966    // Go to previous highlight - should stay on first "variable"
25967    cx.update_editor(|editor, window, cx| {
25968        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
25969    });
25970    cx.assert_editor_state(
25971        "let ˇvariable = 42;
25972let another = variable + 1;
25973let result = variable * 2;",
25974    );
25975}
25976
25977#[track_caller]
25978fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
25979    editor
25980        .all_inlays(cx)
25981        .into_iter()
25982        .filter_map(|inlay| inlay.get_color())
25983        .map(Rgba::from)
25984        .collect()
25985}