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, channel::oneshot};
   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    },
   31    tree_sitter_python,
   32};
   33use language_settings::Formatter;
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::LspSettings,
   42};
   43use serde_json::{self, json};
   44use settings::{
   45    AllLanguageSettingsContent, IndentGuideBackgroundColoring, IndentGuideColoring,
   46    ProjectSettingsContent,
   47};
   48use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   49use std::{
   50    iter,
   51    sync::atomic::{self, AtomicUsize},
   52};
   53use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   54use text::ToPoint as _;
   55use unindent::Unindent;
   56use util::{
   57    assert_set_eq, path,
   58    rel_path::rel_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_extending_selection(cx: &mut TestAppContext) {
  623    init_test(cx, |_| {});
  624
  625    let editor = cx.add_window(|window, cx| {
  626        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  627        build_editor(buffer, window, cx)
  628    });
  629
  630    _ = editor.update(cx, |editor, window, cx| {
  631        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  632        editor.end_selection(window, cx);
  633        assert_eq!(
  634            editor.selections.display_ranges(cx),
  635            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  636        );
  637
  638        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  639        editor.end_selection(window, cx);
  640        assert_eq!(
  641            editor.selections.display_ranges(cx),
  642            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10)]
  643        );
  644
  645        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  646        editor.end_selection(window, cx);
  647        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  648        assert_eq!(
  649            editor.selections.display_ranges(cx),
  650            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  651        );
  652
  653        editor.update_selection(
  654            DisplayPoint::new(DisplayRow(0), 1),
  655            0,
  656            gpui::Point::<f32>::default(),
  657            window,
  658            cx,
  659        );
  660        editor.end_selection(window, cx);
  661        assert_eq!(
  662            editor.selections.display_ranges(cx),
  663            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  664        );
  665
  666        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  667        editor.end_selection(window, cx);
  668        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  669        editor.end_selection(window, cx);
  670        assert_eq!(
  671            editor.selections.display_ranges(cx),
  672            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  673        );
  674
  675        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  676        assert_eq!(
  677            editor.selections.display_ranges(cx),
  678            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  679        );
  680
  681        editor.update_selection(
  682            DisplayPoint::new(DisplayRow(0), 6),
  683            0,
  684            gpui::Point::<f32>::default(),
  685            window,
  686            cx,
  687        );
  688        assert_eq!(
  689            editor.selections.display_ranges(cx),
  690            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  691        );
  692
  693        editor.update_selection(
  694            DisplayPoint::new(DisplayRow(0), 1),
  695            0,
  696            gpui::Point::<f32>::default(),
  697            window,
  698            cx,
  699        );
  700        editor.end_selection(window, cx);
  701        assert_eq!(
  702            editor.selections.display_ranges(cx),
  703            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  704        );
  705    });
  706}
  707
  708#[gpui::test]
  709fn test_clone(cx: &mut TestAppContext) {
  710    init_test(cx, |_| {});
  711
  712    let (text, selection_ranges) = marked_text_ranges(
  713        indoc! {"
  714            one
  715            two
  716            threeˇ
  717            four
  718            fiveˇ
  719        "},
  720        true,
  721    );
  722
  723    let editor = cx.add_window(|window, cx| {
  724        let buffer = MultiBuffer::build_simple(&text, cx);
  725        build_editor(buffer, window, cx)
  726    });
  727
  728    _ = editor.update(cx, |editor, window, cx| {
  729        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  730            s.select_ranges(selection_ranges.clone())
  731        });
  732        editor.fold_creases(
  733            vec![
  734                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  735                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  736            ],
  737            true,
  738            window,
  739            cx,
  740        );
  741    });
  742
  743    let cloned_editor = editor
  744        .update(cx, |editor, _, cx| {
  745            cx.open_window(Default::default(), |window, cx| {
  746                cx.new(|cx| editor.clone(window, cx))
  747            })
  748        })
  749        .unwrap()
  750        .unwrap();
  751
  752    let snapshot = editor
  753        .update(cx, |e, window, cx| e.snapshot(window, cx))
  754        .unwrap();
  755    let cloned_snapshot = cloned_editor
  756        .update(cx, |e, window, cx| e.snapshot(window, cx))
  757        .unwrap();
  758
  759    assert_eq!(
  760        cloned_editor
  761            .update(cx, |e, _, cx| e.display_text(cx))
  762            .unwrap(),
  763        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  764    );
  765    assert_eq!(
  766        cloned_snapshot
  767            .folds_in_range(0..text.len())
  768            .collect::<Vec<_>>(),
  769        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  770    );
  771    assert_set_eq!(
  772        cloned_editor
  773            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  774            .unwrap(),
  775        editor
  776            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  777            .unwrap()
  778    );
  779    assert_set_eq!(
  780        cloned_editor
  781            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  782            .unwrap(),
  783        editor
  784            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  785            .unwrap()
  786    );
  787}
  788
  789#[gpui::test]
  790async fn test_navigation_history(cx: &mut TestAppContext) {
  791    init_test(cx, |_| {});
  792
  793    use workspace::item::Item;
  794
  795    let fs = FakeFs::new(cx.executor());
  796    let project = Project::test(fs, [], cx).await;
  797    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  798    let pane = workspace
  799        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  800        .unwrap();
  801
  802    _ = workspace.update(cx, |_v, window, cx| {
  803        cx.new(|cx| {
  804            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  805            let mut editor = build_editor(buffer, window, cx);
  806            let handle = cx.entity();
  807            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  808
  809            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  810                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  811            }
  812
  813            // Move the cursor a small distance.
  814            // Nothing is added to the navigation history.
  815            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  816                s.select_display_ranges([
  817                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  818                ])
  819            });
  820            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  821                s.select_display_ranges([
  822                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  823                ])
  824            });
  825            assert!(pop_history(&mut editor, cx).is_none());
  826
  827            // Move the cursor a large distance.
  828            // The history can jump back to the previous position.
  829            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  830                s.select_display_ranges([
  831                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  832                ])
  833            });
  834            let nav_entry = pop_history(&mut editor, cx).unwrap();
  835            editor.navigate(nav_entry.data.unwrap(), window, cx);
  836            assert_eq!(nav_entry.item.id(), cx.entity_id());
  837            assert_eq!(
  838                editor.selections.display_ranges(cx),
  839                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  840            );
  841            assert!(pop_history(&mut editor, cx).is_none());
  842
  843            // Move the cursor a small distance via the mouse.
  844            // Nothing is added to the navigation history.
  845            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  846            editor.end_selection(window, cx);
  847            assert_eq!(
  848                editor.selections.display_ranges(cx),
  849                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  850            );
  851            assert!(pop_history(&mut editor, cx).is_none());
  852
  853            // Move the cursor a large distance via the mouse.
  854            // The history can jump back to the previous position.
  855            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  856            editor.end_selection(window, cx);
  857            assert_eq!(
  858                editor.selections.display_ranges(cx),
  859                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  860            );
  861            let nav_entry = pop_history(&mut editor, cx).unwrap();
  862            editor.navigate(nav_entry.data.unwrap(), window, cx);
  863            assert_eq!(nav_entry.item.id(), cx.entity_id());
  864            assert_eq!(
  865                editor.selections.display_ranges(cx),
  866                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  867            );
  868            assert!(pop_history(&mut editor, cx).is_none());
  869
  870            // Set scroll position to check later
  871            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  872            let original_scroll_position = editor.scroll_manager.anchor();
  873
  874            // Jump to the end of the document and adjust scroll
  875            editor.move_to_end(&MoveToEnd, window, cx);
  876            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  877            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  878
  879            let nav_entry = pop_history(&mut editor, cx).unwrap();
  880            editor.navigate(nav_entry.data.unwrap(), window, cx);
  881            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  882
  883            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  884            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  885            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  886            let invalid_point = Point::new(9999, 0);
  887            editor.navigate(
  888                Box::new(NavigationData {
  889                    cursor_anchor: invalid_anchor,
  890                    cursor_position: invalid_point,
  891                    scroll_anchor: ScrollAnchor {
  892                        anchor: invalid_anchor,
  893                        offset: Default::default(),
  894                    },
  895                    scroll_top_row: invalid_point.row,
  896                }),
  897                window,
  898                cx,
  899            );
  900            assert_eq!(
  901                editor.selections.display_ranges(cx),
  902                &[editor.max_point(cx)..editor.max_point(cx)]
  903            );
  904            assert_eq!(
  905                editor.scroll_position(cx),
  906                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  907            );
  908
  909            editor
  910        })
  911    });
  912}
  913
  914#[gpui::test]
  915fn test_cancel(cx: &mut TestAppContext) {
  916    init_test(cx, |_| {});
  917
  918    let editor = cx.add_window(|window, cx| {
  919        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  920        build_editor(buffer, window, cx)
  921    });
  922
  923    _ = editor.update(cx, |editor, window, cx| {
  924        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  925        editor.update_selection(
  926            DisplayPoint::new(DisplayRow(1), 1),
  927            0,
  928            gpui::Point::<f32>::default(),
  929            window,
  930            cx,
  931        );
  932        editor.end_selection(window, cx);
  933
  934        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  935        editor.update_selection(
  936            DisplayPoint::new(DisplayRow(0), 3),
  937            0,
  938            gpui::Point::<f32>::default(),
  939            window,
  940            cx,
  941        );
  942        editor.end_selection(window, cx);
  943        assert_eq!(
  944            editor.selections.display_ranges(cx),
  945            [
  946                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  947                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  948            ]
  949        );
  950    });
  951
  952    _ = editor.update(cx, |editor, window, cx| {
  953        editor.cancel(&Cancel, window, cx);
  954        assert_eq!(
  955            editor.selections.display_ranges(cx),
  956            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  957        );
  958    });
  959
  960    _ = editor.update(cx, |editor, window, cx| {
  961        editor.cancel(&Cancel, window, cx);
  962        assert_eq!(
  963            editor.selections.display_ranges(cx),
  964            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  965        );
  966    });
  967}
  968
  969#[gpui::test]
  970fn test_fold_action(cx: &mut TestAppContext) {
  971    init_test(cx, |_| {});
  972
  973    let editor = cx.add_window(|window, cx| {
  974        let buffer = MultiBuffer::build_simple(
  975            &"
  976                impl Foo {
  977                    // Hello!
  978
  979                    fn a() {
  980                        1
  981                    }
  982
  983                    fn b() {
  984                        2
  985                    }
  986
  987                    fn c() {
  988                        3
  989                    }
  990                }
  991            "
  992            .unindent(),
  993            cx,
  994        );
  995        build_editor(buffer, window, cx)
  996    });
  997
  998    _ = editor.update(cx, |editor, window, cx| {
  999        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1000            s.select_display_ranges([
 1001                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1002            ]);
 1003        });
 1004        editor.fold(&Fold, window, cx);
 1005        assert_eq!(
 1006            editor.display_text(cx),
 1007            "
 1008                impl Foo {
 1009                    // Hello!
 1010
 1011                    fn a() {
 1012                        1
 1013                    }
 1014
 1015                    fn b() {⋯
 1016                    }
 1017
 1018                    fn c() {⋯
 1019                    }
 1020                }
 1021            "
 1022            .unindent(),
 1023        );
 1024
 1025        editor.fold(&Fold, window, cx);
 1026        assert_eq!(
 1027            editor.display_text(cx),
 1028            "
 1029                impl Foo {⋯
 1030                }
 1031            "
 1032            .unindent(),
 1033        );
 1034
 1035        editor.unfold_lines(&UnfoldLines, window, cx);
 1036        assert_eq!(
 1037            editor.display_text(cx),
 1038            "
 1039                impl Foo {
 1040                    // Hello!
 1041
 1042                    fn a() {
 1043                        1
 1044                    }
 1045
 1046                    fn b() {⋯
 1047                    }
 1048
 1049                    fn c() {⋯
 1050                    }
 1051                }
 1052            "
 1053            .unindent(),
 1054        );
 1055
 1056        editor.unfold_lines(&UnfoldLines, window, cx);
 1057        assert_eq!(
 1058            editor.display_text(cx),
 1059            editor.buffer.read(cx).read(cx).text()
 1060        );
 1061    });
 1062}
 1063
 1064#[gpui::test]
 1065fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1066    init_test(cx, |_| {});
 1067
 1068    let editor = cx.add_window(|window, cx| {
 1069        let buffer = MultiBuffer::build_simple(
 1070            &"
 1071                class Foo:
 1072                    # Hello!
 1073
 1074                    def a():
 1075                        print(1)
 1076
 1077                    def b():
 1078                        print(2)
 1079
 1080                    def c():
 1081                        print(3)
 1082            "
 1083            .unindent(),
 1084            cx,
 1085        );
 1086        build_editor(buffer, window, cx)
 1087    });
 1088
 1089    _ = editor.update(cx, |editor, window, cx| {
 1090        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1091            s.select_display_ranges([
 1092                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1093            ]);
 1094        });
 1095        editor.fold(&Fold, window, cx);
 1096        assert_eq!(
 1097            editor.display_text(cx),
 1098            "
 1099                class Foo:
 1100                    # Hello!
 1101
 1102                    def a():
 1103                        print(1)
 1104
 1105                    def b():⋯
 1106
 1107                    def c():⋯
 1108            "
 1109            .unindent(),
 1110        );
 1111
 1112        editor.fold(&Fold, window, cx);
 1113        assert_eq!(
 1114            editor.display_text(cx),
 1115            "
 1116                class Foo:⋯
 1117            "
 1118            .unindent(),
 1119        );
 1120
 1121        editor.unfold_lines(&UnfoldLines, window, cx);
 1122        assert_eq!(
 1123            editor.display_text(cx),
 1124            "
 1125                class Foo:
 1126                    # Hello!
 1127
 1128                    def a():
 1129                        print(1)
 1130
 1131                    def b():⋯
 1132
 1133                    def c():⋯
 1134            "
 1135            .unindent(),
 1136        );
 1137
 1138        editor.unfold_lines(&UnfoldLines, window, cx);
 1139        assert_eq!(
 1140            editor.display_text(cx),
 1141            editor.buffer.read(cx).read(cx).text()
 1142        );
 1143    });
 1144}
 1145
 1146#[gpui::test]
 1147fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1148    init_test(cx, |_| {});
 1149
 1150    let editor = cx.add_window(|window, cx| {
 1151        let buffer = MultiBuffer::build_simple(
 1152            &"
 1153                class Foo:
 1154                    # Hello!
 1155
 1156                    def a():
 1157                        print(1)
 1158
 1159                    def b():
 1160                        print(2)
 1161
 1162
 1163                    def c():
 1164                        print(3)
 1165
 1166
 1167            "
 1168            .unindent(),
 1169            cx,
 1170        );
 1171        build_editor(buffer, window, cx)
 1172    });
 1173
 1174    _ = editor.update(cx, |editor, window, cx| {
 1175        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1176            s.select_display_ranges([
 1177                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1178            ]);
 1179        });
 1180        editor.fold(&Fold, window, cx);
 1181        assert_eq!(
 1182            editor.display_text(cx),
 1183            "
 1184                class Foo:
 1185                    # Hello!
 1186
 1187                    def a():
 1188                        print(1)
 1189
 1190                    def b():⋯
 1191
 1192
 1193                    def c():⋯
 1194
 1195
 1196            "
 1197            .unindent(),
 1198        );
 1199
 1200        editor.fold(&Fold, window, cx);
 1201        assert_eq!(
 1202            editor.display_text(cx),
 1203            "
 1204                class Foo:⋯
 1205
 1206
 1207            "
 1208            .unindent(),
 1209        );
 1210
 1211        editor.unfold_lines(&UnfoldLines, window, cx);
 1212        assert_eq!(
 1213            editor.display_text(cx),
 1214            "
 1215                class Foo:
 1216                    # Hello!
 1217
 1218                    def a():
 1219                        print(1)
 1220
 1221                    def b():⋯
 1222
 1223
 1224                    def c():⋯
 1225
 1226
 1227            "
 1228            .unindent(),
 1229        );
 1230
 1231        editor.unfold_lines(&UnfoldLines, window, cx);
 1232        assert_eq!(
 1233            editor.display_text(cx),
 1234            editor.buffer.read(cx).read(cx).text()
 1235        );
 1236    });
 1237}
 1238
 1239#[gpui::test]
 1240fn test_fold_at_level(cx: &mut TestAppContext) {
 1241    init_test(cx, |_| {});
 1242
 1243    let editor = cx.add_window(|window, cx| {
 1244        let buffer = MultiBuffer::build_simple(
 1245            &"
 1246                class Foo:
 1247                    # Hello!
 1248
 1249                    def a():
 1250                        print(1)
 1251
 1252                    def b():
 1253                        print(2)
 1254
 1255
 1256                class Bar:
 1257                    # World!
 1258
 1259                    def a():
 1260                        print(1)
 1261
 1262                    def b():
 1263                        print(2)
 1264
 1265
 1266            "
 1267            .unindent(),
 1268            cx,
 1269        );
 1270        build_editor(buffer, window, cx)
 1271    });
 1272
 1273    _ = editor.update(cx, |editor, window, cx| {
 1274        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1275        assert_eq!(
 1276            editor.display_text(cx),
 1277            "
 1278                class Foo:
 1279                    # Hello!
 1280
 1281                    def a():⋯
 1282
 1283                    def b():⋯
 1284
 1285
 1286                class Bar:
 1287                    # World!
 1288
 1289                    def a():⋯
 1290
 1291                    def b():⋯
 1292
 1293
 1294            "
 1295            .unindent(),
 1296        );
 1297
 1298        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1299        assert_eq!(
 1300            editor.display_text(cx),
 1301            "
 1302                class Foo:⋯
 1303
 1304
 1305                class Bar:⋯
 1306
 1307
 1308            "
 1309            .unindent(),
 1310        );
 1311
 1312        editor.unfold_all(&UnfoldAll, window, cx);
 1313        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1314        assert_eq!(
 1315            editor.display_text(cx),
 1316            "
 1317                class Foo:
 1318                    # Hello!
 1319
 1320                    def a():
 1321                        print(1)
 1322
 1323                    def b():
 1324                        print(2)
 1325
 1326
 1327                class Bar:
 1328                    # World!
 1329
 1330                    def a():
 1331                        print(1)
 1332
 1333                    def b():
 1334                        print(2)
 1335
 1336
 1337            "
 1338            .unindent(),
 1339        );
 1340
 1341        assert_eq!(
 1342            editor.display_text(cx),
 1343            editor.buffer.read(cx).read(cx).text()
 1344        );
 1345        let (_, positions) = marked_text_ranges(
 1346            &"
 1347                       class Foo:
 1348                           # Hello!
 1349
 1350                           def a():
 1351                              print(1)
 1352
 1353                           def b():
 1354                               p«riˇ»nt(2)
 1355
 1356
 1357                       class Bar:
 1358                           # World!
 1359
 1360                           def a():
 1361                               «ˇprint(1)
 1362
 1363                           def b():
 1364                               print(2)»
 1365
 1366
 1367                   "
 1368            .unindent(),
 1369            true,
 1370        );
 1371
 1372        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1373            s.select_ranges(positions)
 1374        });
 1375
 1376        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1377        assert_eq!(
 1378            editor.display_text(cx),
 1379            "
 1380                class Foo:
 1381                    # Hello!
 1382
 1383                    def a():⋯
 1384
 1385                    def b():
 1386                        print(2)
 1387
 1388
 1389                class Bar:
 1390                    # World!
 1391
 1392                    def a():
 1393                        print(1)
 1394
 1395                    def b():
 1396                        print(2)
 1397
 1398
 1399            "
 1400            .unindent(),
 1401        );
 1402    });
 1403}
 1404
 1405#[gpui::test]
 1406fn test_move_cursor(cx: &mut TestAppContext) {
 1407    init_test(cx, |_| {});
 1408
 1409    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1410    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1411
 1412    buffer.update(cx, |buffer, cx| {
 1413        buffer.edit(
 1414            vec![
 1415                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1416                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1417            ],
 1418            None,
 1419            cx,
 1420        );
 1421    });
 1422    _ = editor.update(cx, |editor, window, cx| {
 1423        assert_eq!(
 1424            editor.selections.display_ranges(cx),
 1425            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1426        );
 1427
 1428        editor.move_down(&MoveDown, window, cx);
 1429        assert_eq!(
 1430            editor.selections.display_ranges(cx),
 1431            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1432        );
 1433
 1434        editor.move_right(&MoveRight, window, cx);
 1435        assert_eq!(
 1436            editor.selections.display_ranges(cx),
 1437            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1438        );
 1439
 1440        editor.move_left(&MoveLeft, window, cx);
 1441        assert_eq!(
 1442            editor.selections.display_ranges(cx),
 1443            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1444        );
 1445
 1446        editor.move_up(&MoveUp, window, cx);
 1447        assert_eq!(
 1448            editor.selections.display_ranges(cx),
 1449            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1450        );
 1451
 1452        editor.move_to_end(&MoveToEnd, window, cx);
 1453        assert_eq!(
 1454            editor.selections.display_ranges(cx),
 1455            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1456        );
 1457
 1458        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1459        assert_eq!(
 1460            editor.selections.display_ranges(cx),
 1461            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1462        );
 1463
 1464        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1465            s.select_display_ranges([
 1466                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1467            ]);
 1468        });
 1469        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1470        assert_eq!(
 1471            editor.selections.display_ranges(cx),
 1472            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1473        );
 1474
 1475        editor.select_to_end(&SelectToEnd, window, cx);
 1476        assert_eq!(
 1477            editor.selections.display_ranges(cx),
 1478            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1479        );
 1480    });
 1481}
 1482
 1483#[gpui::test]
 1484fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1485    init_test(cx, |_| {});
 1486
 1487    let editor = cx.add_window(|window, cx| {
 1488        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1489        build_editor(buffer, window, cx)
 1490    });
 1491
 1492    assert_eq!('🟥'.len_utf8(), 4);
 1493    assert_eq!('α'.len_utf8(), 2);
 1494
 1495    _ = editor.update(cx, |editor, window, cx| {
 1496        editor.fold_creases(
 1497            vec![
 1498                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1499                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1500                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1501            ],
 1502            true,
 1503            window,
 1504            cx,
 1505        );
 1506        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1507
 1508        editor.move_right(&MoveRight, window, cx);
 1509        assert_eq!(
 1510            editor.selections.display_ranges(cx),
 1511            &[empty_range(0, "🟥".len())]
 1512        );
 1513        editor.move_right(&MoveRight, window, cx);
 1514        assert_eq!(
 1515            editor.selections.display_ranges(cx),
 1516            &[empty_range(0, "🟥🟧".len())]
 1517        );
 1518        editor.move_right(&MoveRight, window, cx);
 1519        assert_eq!(
 1520            editor.selections.display_ranges(cx),
 1521            &[empty_range(0, "🟥🟧⋯".len())]
 1522        );
 1523
 1524        editor.move_down(&MoveDown, window, cx);
 1525        assert_eq!(
 1526            editor.selections.display_ranges(cx),
 1527            &[empty_range(1, "ab⋯e".len())]
 1528        );
 1529        editor.move_left(&MoveLeft, window, cx);
 1530        assert_eq!(
 1531            editor.selections.display_ranges(cx),
 1532            &[empty_range(1, "ab⋯".len())]
 1533        );
 1534        editor.move_left(&MoveLeft, window, cx);
 1535        assert_eq!(
 1536            editor.selections.display_ranges(cx),
 1537            &[empty_range(1, "ab".len())]
 1538        );
 1539        editor.move_left(&MoveLeft, window, cx);
 1540        assert_eq!(
 1541            editor.selections.display_ranges(cx),
 1542            &[empty_range(1, "a".len())]
 1543        );
 1544
 1545        editor.move_down(&MoveDown, window, cx);
 1546        assert_eq!(
 1547            editor.selections.display_ranges(cx),
 1548            &[empty_range(2, "α".len())]
 1549        );
 1550        editor.move_right(&MoveRight, window, cx);
 1551        assert_eq!(
 1552            editor.selections.display_ranges(cx),
 1553            &[empty_range(2, "αβ".len())]
 1554        );
 1555        editor.move_right(&MoveRight, window, cx);
 1556        assert_eq!(
 1557            editor.selections.display_ranges(cx),
 1558            &[empty_range(2, "αβ⋯".len())]
 1559        );
 1560        editor.move_right(&MoveRight, window, cx);
 1561        assert_eq!(
 1562            editor.selections.display_ranges(cx),
 1563            &[empty_range(2, "αβ⋯ε".len())]
 1564        );
 1565
 1566        editor.move_up(&MoveUp, window, cx);
 1567        assert_eq!(
 1568            editor.selections.display_ranges(cx),
 1569            &[empty_range(1, "ab⋯e".len())]
 1570        );
 1571        editor.move_down(&MoveDown, window, cx);
 1572        assert_eq!(
 1573            editor.selections.display_ranges(cx),
 1574            &[empty_range(2, "αβ⋯ε".len())]
 1575        );
 1576        editor.move_up(&MoveUp, window, cx);
 1577        assert_eq!(
 1578            editor.selections.display_ranges(cx),
 1579            &[empty_range(1, "ab⋯e".len())]
 1580        );
 1581
 1582        editor.move_up(&MoveUp, window, cx);
 1583        assert_eq!(
 1584            editor.selections.display_ranges(cx),
 1585            &[empty_range(0, "🟥🟧".len())]
 1586        );
 1587        editor.move_left(&MoveLeft, window, cx);
 1588        assert_eq!(
 1589            editor.selections.display_ranges(cx),
 1590            &[empty_range(0, "🟥".len())]
 1591        );
 1592        editor.move_left(&MoveLeft, window, cx);
 1593        assert_eq!(
 1594            editor.selections.display_ranges(cx),
 1595            &[empty_range(0, "".len())]
 1596        );
 1597    });
 1598}
 1599
 1600#[gpui::test]
 1601fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1602    init_test(cx, |_| {});
 1603
 1604    let editor = cx.add_window(|window, cx| {
 1605        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1606        build_editor(buffer, window, cx)
 1607    });
 1608    _ = editor.update(cx, |editor, window, cx| {
 1609        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1610            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1611        });
 1612
 1613        // moving above start of document should move selection to start of document,
 1614        // but the next move down should still be at the original goal_x
 1615        editor.move_up(&MoveUp, window, cx);
 1616        assert_eq!(
 1617            editor.selections.display_ranges(cx),
 1618            &[empty_range(0, "".len())]
 1619        );
 1620
 1621        editor.move_down(&MoveDown, window, cx);
 1622        assert_eq!(
 1623            editor.selections.display_ranges(cx),
 1624            &[empty_range(1, "abcd".len())]
 1625        );
 1626
 1627        editor.move_down(&MoveDown, window, cx);
 1628        assert_eq!(
 1629            editor.selections.display_ranges(cx),
 1630            &[empty_range(2, "αβγ".len())]
 1631        );
 1632
 1633        editor.move_down(&MoveDown, window, cx);
 1634        assert_eq!(
 1635            editor.selections.display_ranges(cx),
 1636            &[empty_range(3, "abcd".len())]
 1637        );
 1638
 1639        editor.move_down(&MoveDown, window, cx);
 1640        assert_eq!(
 1641            editor.selections.display_ranges(cx),
 1642            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1643        );
 1644
 1645        // moving past end of document should not change goal_x
 1646        editor.move_down(&MoveDown, window, cx);
 1647        assert_eq!(
 1648            editor.selections.display_ranges(cx),
 1649            &[empty_range(5, "".len())]
 1650        );
 1651
 1652        editor.move_down(&MoveDown, window, cx);
 1653        assert_eq!(
 1654            editor.selections.display_ranges(cx),
 1655            &[empty_range(5, "".len())]
 1656        );
 1657
 1658        editor.move_up(&MoveUp, window, cx);
 1659        assert_eq!(
 1660            editor.selections.display_ranges(cx),
 1661            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1662        );
 1663
 1664        editor.move_up(&MoveUp, window, cx);
 1665        assert_eq!(
 1666            editor.selections.display_ranges(cx),
 1667            &[empty_range(3, "abcd".len())]
 1668        );
 1669
 1670        editor.move_up(&MoveUp, window, cx);
 1671        assert_eq!(
 1672            editor.selections.display_ranges(cx),
 1673            &[empty_range(2, "αβγ".len())]
 1674        );
 1675    });
 1676}
 1677
 1678#[gpui::test]
 1679fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1680    init_test(cx, |_| {});
 1681    let move_to_beg = MoveToBeginningOfLine {
 1682        stop_at_soft_wraps: true,
 1683        stop_at_indent: true,
 1684    };
 1685
 1686    let delete_to_beg = DeleteToBeginningOfLine {
 1687        stop_at_indent: false,
 1688    };
 1689
 1690    let move_to_end = MoveToEndOfLine {
 1691        stop_at_soft_wraps: true,
 1692    };
 1693
 1694    let editor = cx.add_window(|window, cx| {
 1695        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1696        build_editor(buffer, window, cx)
 1697    });
 1698    _ = editor.update(cx, |editor, window, cx| {
 1699        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1700            s.select_display_ranges([
 1701                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1702                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1703            ]);
 1704        });
 1705    });
 1706
 1707    _ = editor.update(cx, |editor, window, cx| {
 1708        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1709        assert_eq!(
 1710            editor.selections.display_ranges(cx),
 1711            &[
 1712                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1713                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1714            ]
 1715        );
 1716    });
 1717
 1718    _ = editor.update(cx, |editor, window, cx| {
 1719        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1720        assert_eq!(
 1721            editor.selections.display_ranges(cx),
 1722            &[
 1723                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1724                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1725            ]
 1726        );
 1727    });
 1728
 1729    _ = editor.update(cx, |editor, window, cx| {
 1730        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1731        assert_eq!(
 1732            editor.selections.display_ranges(cx),
 1733            &[
 1734                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1735                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1736            ]
 1737        );
 1738    });
 1739
 1740    _ = editor.update(cx, |editor, window, cx| {
 1741        editor.move_to_end_of_line(&move_to_end, window, cx);
 1742        assert_eq!(
 1743            editor.selections.display_ranges(cx),
 1744            &[
 1745                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1746                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1747            ]
 1748        );
 1749    });
 1750
 1751    // Moving to the end of line again is a no-op.
 1752    _ = editor.update(cx, |editor, window, cx| {
 1753        editor.move_to_end_of_line(&move_to_end, window, cx);
 1754        assert_eq!(
 1755            editor.selections.display_ranges(cx),
 1756            &[
 1757                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1758                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1759            ]
 1760        );
 1761    });
 1762
 1763    _ = editor.update(cx, |editor, window, cx| {
 1764        editor.move_left(&MoveLeft, window, cx);
 1765        editor.select_to_beginning_of_line(
 1766            &SelectToBeginningOfLine {
 1767                stop_at_soft_wraps: true,
 1768                stop_at_indent: true,
 1769            },
 1770            window,
 1771            cx,
 1772        );
 1773        assert_eq!(
 1774            editor.selections.display_ranges(cx),
 1775            &[
 1776                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1777                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1778            ]
 1779        );
 1780    });
 1781
 1782    _ = editor.update(cx, |editor, window, cx| {
 1783        editor.select_to_beginning_of_line(
 1784            &SelectToBeginningOfLine {
 1785                stop_at_soft_wraps: true,
 1786                stop_at_indent: true,
 1787            },
 1788            window,
 1789            cx,
 1790        );
 1791        assert_eq!(
 1792            editor.selections.display_ranges(cx),
 1793            &[
 1794                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1795                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1796            ]
 1797        );
 1798    });
 1799
 1800    _ = editor.update(cx, |editor, window, cx| {
 1801        editor.select_to_beginning_of_line(
 1802            &SelectToBeginningOfLine {
 1803                stop_at_soft_wraps: true,
 1804                stop_at_indent: true,
 1805            },
 1806            window,
 1807            cx,
 1808        );
 1809        assert_eq!(
 1810            editor.selections.display_ranges(cx),
 1811            &[
 1812                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1813                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1814            ]
 1815        );
 1816    });
 1817
 1818    _ = editor.update(cx, |editor, window, cx| {
 1819        editor.select_to_end_of_line(
 1820            &SelectToEndOfLine {
 1821                stop_at_soft_wraps: true,
 1822            },
 1823            window,
 1824            cx,
 1825        );
 1826        assert_eq!(
 1827            editor.selections.display_ranges(cx),
 1828            &[
 1829                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1830                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1831            ]
 1832        );
 1833    });
 1834
 1835    _ = editor.update(cx, |editor, window, cx| {
 1836        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1837        assert_eq!(editor.display_text(cx), "ab\n  de");
 1838        assert_eq!(
 1839            editor.selections.display_ranges(cx),
 1840            &[
 1841                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1842                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1843            ]
 1844        );
 1845    });
 1846
 1847    _ = editor.update(cx, |editor, window, cx| {
 1848        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1849        assert_eq!(editor.display_text(cx), "\n");
 1850        assert_eq!(
 1851            editor.selections.display_ranges(cx),
 1852            &[
 1853                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1854                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1855            ]
 1856        );
 1857    });
 1858}
 1859
 1860#[gpui::test]
 1861fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1862    init_test(cx, |_| {});
 1863    let move_to_beg = MoveToBeginningOfLine {
 1864        stop_at_soft_wraps: false,
 1865        stop_at_indent: false,
 1866    };
 1867
 1868    let move_to_end = MoveToEndOfLine {
 1869        stop_at_soft_wraps: false,
 1870    };
 1871
 1872    let editor = cx.add_window(|window, cx| {
 1873        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1874        build_editor(buffer, window, cx)
 1875    });
 1876
 1877    _ = editor.update(cx, |editor, window, cx| {
 1878        editor.set_wrap_width(Some(140.0.into()), cx);
 1879
 1880        // We expect the following lines after wrapping
 1881        // ```
 1882        // thequickbrownfox
 1883        // jumpedoverthelazydo
 1884        // gs
 1885        // ```
 1886        // The final `gs` was soft-wrapped onto a new line.
 1887        assert_eq!(
 1888            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1889            editor.display_text(cx),
 1890        );
 1891
 1892        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1893        // Start the cursor at the `k` on the first line
 1894        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1895            s.select_display_ranges([
 1896                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1897            ]);
 1898        });
 1899
 1900        // Moving to the beginning of the line should put us at the beginning of the line.
 1901        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1902        assert_eq!(
 1903            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1904            editor.selections.display_ranges(cx)
 1905        );
 1906
 1907        // Moving to the end of the line should put us at the end of the line.
 1908        editor.move_to_end_of_line(&move_to_end, window, cx);
 1909        assert_eq!(
 1910            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1911            editor.selections.display_ranges(cx)
 1912        );
 1913
 1914        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1915        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1916        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1917            s.select_display_ranges([
 1918                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1919            ]);
 1920        });
 1921
 1922        // Moving to the beginning of the line should put us at the start of the second line of
 1923        // display text, i.e., the `j`.
 1924        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1925        assert_eq!(
 1926            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1927            editor.selections.display_ranges(cx)
 1928        );
 1929
 1930        // Moving to the beginning of the line again should be a no-op.
 1931        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1932        assert_eq!(
 1933            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1934            editor.selections.display_ranges(cx)
 1935        );
 1936
 1937        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1938        // next display line.
 1939        editor.move_to_end_of_line(&move_to_end, window, cx);
 1940        assert_eq!(
 1941            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1942            editor.selections.display_ranges(cx)
 1943        );
 1944
 1945        // Moving to the end of the line again should be a no-op.
 1946        editor.move_to_end_of_line(&move_to_end, window, cx);
 1947        assert_eq!(
 1948            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1949            editor.selections.display_ranges(cx)
 1950        );
 1951    });
 1952}
 1953
 1954#[gpui::test]
 1955fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1956    init_test(cx, |_| {});
 1957
 1958    let move_to_beg = MoveToBeginningOfLine {
 1959        stop_at_soft_wraps: true,
 1960        stop_at_indent: true,
 1961    };
 1962
 1963    let select_to_beg = SelectToBeginningOfLine {
 1964        stop_at_soft_wraps: true,
 1965        stop_at_indent: true,
 1966    };
 1967
 1968    let delete_to_beg = DeleteToBeginningOfLine {
 1969        stop_at_indent: true,
 1970    };
 1971
 1972    let move_to_end = MoveToEndOfLine {
 1973        stop_at_soft_wraps: false,
 1974    };
 1975
 1976    let editor = cx.add_window(|window, cx| {
 1977        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1978        build_editor(buffer, window, cx)
 1979    });
 1980
 1981    _ = editor.update(cx, |editor, window, cx| {
 1982        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1983            s.select_display_ranges([
 1984                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1985                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1986            ]);
 1987        });
 1988
 1989        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1990        // and the second cursor at the first non-whitespace character in the line.
 1991        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1992        assert_eq!(
 1993            editor.selections.display_ranges(cx),
 1994            &[
 1995                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1996                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1997            ]
 1998        );
 1999
 2000        // Moving to the beginning of the line again should be a no-op for the first cursor,
 2001        // and should move the second cursor to the beginning of the line.
 2002        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2003        assert_eq!(
 2004            editor.selections.display_ranges(cx),
 2005            &[
 2006                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2007                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 2008            ]
 2009        );
 2010
 2011        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 2012        // and should move the second cursor back to the first non-whitespace character in the line.
 2013        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2014        assert_eq!(
 2015            editor.selections.display_ranges(cx),
 2016            &[
 2017                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2018                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2019            ]
 2020        );
 2021
 2022        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 2023        // and to the first non-whitespace character in the line for the second cursor.
 2024        editor.move_to_end_of_line(&move_to_end, window, cx);
 2025        editor.move_left(&MoveLeft, window, cx);
 2026        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2027        assert_eq!(
 2028            editor.selections.display_ranges(cx),
 2029            &[
 2030                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2031                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 2032            ]
 2033        );
 2034
 2035        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2036        // and should select to the beginning of the line for the second cursor.
 2037        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2038        assert_eq!(
 2039            editor.selections.display_ranges(cx),
 2040            &[
 2041                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2042                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2043            ]
 2044        );
 2045
 2046        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2047        // and should delete to the first non-whitespace character in the line for the second cursor.
 2048        editor.move_to_end_of_line(&move_to_end, window, cx);
 2049        editor.move_left(&MoveLeft, window, cx);
 2050        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2051        assert_eq!(editor.text(cx), "c\n  f");
 2052    });
 2053}
 2054
 2055#[gpui::test]
 2056fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2057    init_test(cx, |_| {});
 2058
 2059    let move_to_beg = MoveToBeginningOfLine {
 2060        stop_at_soft_wraps: true,
 2061        stop_at_indent: true,
 2062    };
 2063
 2064    let editor = cx.add_window(|window, cx| {
 2065        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2066        build_editor(buffer, window, cx)
 2067    });
 2068
 2069    _ = editor.update(cx, |editor, window, cx| {
 2070        // test cursor between line_start and indent_start
 2071        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2072            s.select_display_ranges([
 2073                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2074            ]);
 2075        });
 2076
 2077        // cursor should move to line_start
 2078        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2079        assert_eq!(
 2080            editor.selections.display_ranges(cx),
 2081            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2082        );
 2083
 2084        // cursor should move to indent_start
 2085        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2086        assert_eq!(
 2087            editor.selections.display_ranges(cx),
 2088            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2089        );
 2090
 2091        // cursor should move to back to line_start
 2092        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2093        assert_eq!(
 2094            editor.selections.display_ranges(cx),
 2095            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2096        );
 2097    });
 2098}
 2099
 2100#[gpui::test]
 2101fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2102    init_test(cx, |_| {});
 2103
 2104    let editor = cx.add_window(|window, cx| {
 2105        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2106        build_editor(buffer, window, cx)
 2107    });
 2108    _ = editor.update(cx, |editor, window, cx| {
 2109        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2110            s.select_display_ranges([
 2111                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2112                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2113            ])
 2114        });
 2115        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2116        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2117
 2118        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2119        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2120
 2121        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2122        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2123
 2124        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2125        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2126
 2127        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2128        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2129
 2130        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2131        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2132
 2133        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2134        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2135
 2136        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2137        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2138
 2139        editor.move_right(&MoveRight, window, cx);
 2140        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2141        assert_selection_ranges(
 2142            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2143            editor,
 2144            cx,
 2145        );
 2146
 2147        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2148        assert_selection_ranges(
 2149            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2150            editor,
 2151            cx,
 2152        );
 2153
 2154        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2155        assert_selection_ranges(
 2156            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2157            editor,
 2158            cx,
 2159        );
 2160    });
 2161}
 2162
 2163#[gpui::test]
 2164fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2165    init_test(cx, |_| {});
 2166
 2167    let editor = cx.add_window(|window, cx| {
 2168        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2169        build_editor(buffer, window, cx)
 2170    });
 2171
 2172    _ = editor.update(cx, |editor, window, cx| {
 2173        editor.set_wrap_width(Some(140.0.into()), cx);
 2174        assert_eq!(
 2175            editor.display_text(cx),
 2176            "use one::{\n    two::three::\n    four::five\n};"
 2177        );
 2178
 2179        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2180            s.select_display_ranges([
 2181                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2182            ]);
 2183        });
 2184
 2185        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2186        assert_eq!(
 2187            editor.selections.display_ranges(cx),
 2188            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2189        );
 2190
 2191        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2192        assert_eq!(
 2193            editor.selections.display_ranges(cx),
 2194            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2195        );
 2196
 2197        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2198        assert_eq!(
 2199            editor.selections.display_ranges(cx),
 2200            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2201        );
 2202
 2203        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2204        assert_eq!(
 2205            editor.selections.display_ranges(cx),
 2206            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2207        );
 2208
 2209        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2210        assert_eq!(
 2211            editor.selections.display_ranges(cx),
 2212            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2213        );
 2214
 2215        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2216        assert_eq!(
 2217            editor.selections.display_ranges(cx),
 2218            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2219        );
 2220    });
 2221}
 2222
 2223#[gpui::test]
 2224async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2225    init_test(cx, |_| {});
 2226    let mut cx = EditorTestContext::new(cx).await;
 2227
 2228    let line_height = cx.editor(|editor, window, _| {
 2229        editor
 2230            .style()
 2231            .unwrap()
 2232            .text
 2233            .line_height_in_pixels(window.rem_size())
 2234    });
 2235    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2236
 2237    cx.set_state(
 2238        &r#"ˇone
 2239        two
 2240
 2241        three
 2242        fourˇ
 2243        five
 2244
 2245        six"#
 2246            .unindent(),
 2247    );
 2248
 2249    cx.update_editor(|editor, window, cx| {
 2250        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2251    });
 2252    cx.assert_editor_state(
 2253        &r#"one
 2254        two
 2255        ˇ
 2256        three
 2257        four
 2258        five
 2259        ˇ
 2260        six"#
 2261            .unindent(),
 2262    );
 2263
 2264    cx.update_editor(|editor, window, cx| {
 2265        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2266    });
 2267    cx.assert_editor_state(
 2268        &r#"one
 2269        two
 2270
 2271        three
 2272        four
 2273        five
 2274        ˇ
 2275        sixˇ"#
 2276            .unindent(),
 2277    );
 2278
 2279    cx.update_editor(|editor, window, cx| {
 2280        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2281    });
 2282    cx.assert_editor_state(
 2283        &r#"one
 2284        two
 2285
 2286        three
 2287        four
 2288        five
 2289
 2290        sixˇ"#
 2291            .unindent(),
 2292    );
 2293
 2294    cx.update_editor(|editor, window, cx| {
 2295        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2296    });
 2297    cx.assert_editor_state(
 2298        &r#"one
 2299        two
 2300
 2301        three
 2302        four
 2303        five
 2304        ˇ
 2305        six"#
 2306            .unindent(),
 2307    );
 2308
 2309    cx.update_editor(|editor, window, cx| {
 2310        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2311    });
 2312    cx.assert_editor_state(
 2313        &r#"one
 2314        two
 2315        ˇ
 2316        three
 2317        four
 2318        five
 2319
 2320        six"#
 2321            .unindent(),
 2322    );
 2323
 2324    cx.update_editor(|editor, window, cx| {
 2325        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2326    });
 2327    cx.assert_editor_state(
 2328        &r#"ˇone
 2329        two
 2330
 2331        three
 2332        four
 2333        five
 2334
 2335        six"#
 2336            .unindent(),
 2337    );
 2338}
 2339
 2340#[gpui::test]
 2341async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2342    init_test(cx, |_| {});
 2343    let mut cx = EditorTestContext::new(cx).await;
 2344    let line_height = cx.editor(|editor, window, _| {
 2345        editor
 2346            .style()
 2347            .unwrap()
 2348            .text
 2349            .line_height_in_pixels(window.rem_size())
 2350    });
 2351    let window = cx.window;
 2352    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2353
 2354    cx.set_state(
 2355        r#"ˇone
 2356        two
 2357        three
 2358        four
 2359        five
 2360        six
 2361        seven
 2362        eight
 2363        nine
 2364        ten
 2365        "#,
 2366    );
 2367
 2368    cx.update_editor(|editor, window, cx| {
 2369        assert_eq!(
 2370            editor.snapshot(window, cx).scroll_position(),
 2371            gpui::Point::new(0., 0.)
 2372        );
 2373        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2374        assert_eq!(
 2375            editor.snapshot(window, cx).scroll_position(),
 2376            gpui::Point::new(0., 3.)
 2377        );
 2378        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2379        assert_eq!(
 2380            editor.snapshot(window, cx).scroll_position(),
 2381            gpui::Point::new(0., 6.)
 2382        );
 2383        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2384        assert_eq!(
 2385            editor.snapshot(window, cx).scroll_position(),
 2386            gpui::Point::new(0., 3.)
 2387        );
 2388
 2389        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2390        assert_eq!(
 2391            editor.snapshot(window, cx).scroll_position(),
 2392            gpui::Point::new(0., 1.)
 2393        );
 2394        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2395        assert_eq!(
 2396            editor.snapshot(window, cx).scroll_position(),
 2397            gpui::Point::new(0., 3.)
 2398        );
 2399    });
 2400}
 2401
 2402#[gpui::test]
 2403async fn test_autoscroll(cx: &mut TestAppContext) {
 2404    init_test(cx, |_| {});
 2405    let mut cx = EditorTestContext::new(cx).await;
 2406
 2407    let line_height = cx.update_editor(|editor, window, cx| {
 2408        editor.set_vertical_scroll_margin(2, cx);
 2409        editor
 2410            .style()
 2411            .unwrap()
 2412            .text
 2413            .line_height_in_pixels(window.rem_size())
 2414    });
 2415    let window = cx.window;
 2416    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2417
 2418    cx.set_state(
 2419        r#"ˇone
 2420            two
 2421            three
 2422            four
 2423            five
 2424            six
 2425            seven
 2426            eight
 2427            nine
 2428            ten
 2429        "#,
 2430    );
 2431    cx.update_editor(|editor, window, cx| {
 2432        assert_eq!(
 2433            editor.snapshot(window, cx).scroll_position(),
 2434            gpui::Point::new(0., 0.0)
 2435        );
 2436    });
 2437
 2438    // Add a cursor below the visible area. Since both cursors cannot fit
 2439    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2440    // allows the vertical scroll margin below that cursor.
 2441    cx.update_editor(|editor, window, cx| {
 2442        editor.change_selections(Default::default(), window, cx, |selections| {
 2443            selections.select_ranges([
 2444                Point::new(0, 0)..Point::new(0, 0),
 2445                Point::new(6, 0)..Point::new(6, 0),
 2446            ]);
 2447        })
 2448    });
 2449    cx.update_editor(|editor, window, cx| {
 2450        assert_eq!(
 2451            editor.snapshot(window, cx).scroll_position(),
 2452            gpui::Point::new(0., 3.0)
 2453        );
 2454    });
 2455
 2456    // Move down. The editor cursor scrolls down to track the newest cursor.
 2457    cx.update_editor(|editor, window, cx| {
 2458        editor.move_down(&Default::default(), window, cx);
 2459    });
 2460    cx.update_editor(|editor, window, cx| {
 2461        assert_eq!(
 2462            editor.snapshot(window, cx).scroll_position(),
 2463            gpui::Point::new(0., 4.0)
 2464        );
 2465    });
 2466
 2467    // Add a cursor above the visible area. Since both cursors fit on screen,
 2468    // the editor scrolls to show both.
 2469    cx.update_editor(|editor, window, cx| {
 2470        editor.change_selections(Default::default(), window, cx, |selections| {
 2471            selections.select_ranges([
 2472                Point::new(1, 0)..Point::new(1, 0),
 2473                Point::new(6, 0)..Point::new(6, 0),
 2474            ]);
 2475        })
 2476    });
 2477    cx.update_editor(|editor, window, cx| {
 2478        assert_eq!(
 2479            editor.snapshot(window, cx).scroll_position(),
 2480            gpui::Point::new(0., 1.0)
 2481        );
 2482    });
 2483}
 2484
 2485#[gpui::test]
 2486async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2487    init_test(cx, |_| {});
 2488    let mut cx = EditorTestContext::new(cx).await;
 2489
 2490    let line_height = cx.editor(|editor, window, _cx| {
 2491        editor
 2492            .style()
 2493            .unwrap()
 2494            .text
 2495            .line_height_in_pixels(window.rem_size())
 2496    });
 2497    let window = cx.window;
 2498    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2499    cx.set_state(
 2500        &r#"
 2501        ˇone
 2502        two
 2503        threeˇ
 2504        four
 2505        five
 2506        six
 2507        seven
 2508        eight
 2509        nine
 2510        ten
 2511        "#
 2512        .unindent(),
 2513    );
 2514
 2515    cx.update_editor(|editor, window, cx| {
 2516        editor.move_page_down(&MovePageDown::default(), window, cx)
 2517    });
 2518    cx.assert_editor_state(
 2519        &r#"
 2520        one
 2521        two
 2522        three
 2523        ˇfour
 2524        five
 2525        sixˇ
 2526        seven
 2527        eight
 2528        nine
 2529        ten
 2530        "#
 2531        .unindent(),
 2532    );
 2533
 2534    cx.update_editor(|editor, window, cx| {
 2535        editor.move_page_down(&MovePageDown::default(), window, cx)
 2536    });
 2537    cx.assert_editor_state(
 2538        &r#"
 2539        one
 2540        two
 2541        three
 2542        four
 2543        five
 2544        six
 2545        ˇseven
 2546        eight
 2547        nineˇ
 2548        ten
 2549        "#
 2550        .unindent(),
 2551    );
 2552
 2553    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2554    cx.assert_editor_state(
 2555        &r#"
 2556        one
 2557        two
 2558        three
 2559        ˇfour
 2560        five
 2561        sixˇ
 2562        seven
 2563        eight
 2564        nine
 2565        ten
 2566        "#
 2567        .unindent(),
 2568    );
 2569
 2570    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2571    cx.assert_editor_state(
 2572        &r#"
 2573        ˇone
 2574        two
 2575        threeˇ
 2576        four
 2577        five
 2578        six
 2579        seven
 2580        eight
 2581        nine
 2582        ten
 2583        "#
 2584        .unindent(),
 2585    );
 2586
 2587    // Test select collapsing
 2588    cx.update_editor(|editor, window, cx| {
 2589        editor.move_page_down(&MovePageDown::default(), window, cx);
 2590        editor.move_page_down(&MovePageDown::default(), window, cx);
 2591        editor.move_page_down(&MovePageDown::default(), window, cx);
 2592    });
 2593    cx.assert_editor_state(
 2594        &r#"
 2595        one
 2596        two
 2597        three
 2598        four
 2599        five
 2600        six
 2601        seven
 2602        eight
 2603        nine
 2604        ˇten
 2605        ˇ"#
 2606        .unindent(),
 2607    );
 2608}
 2609
 2610#[gpui::test]
 2611async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2612    init_test(cx, |_| {});
 2613    let mut cx = EditorTestContext::new(cx).await;
 2614    cx.set_state("one «two threeˇ» four");
 2615    cx.update_editor(|editor, window, cx| {
 2616        editor.delete_to_beginning_of_line(
 2617            &DeleteToBeginningOfLine {
 2618                stop_at_indent: false,
 2619            },
 2620            window,
 2621            cx,
 2622        );
 2623        assert_eq!(editor.text(cx), " four");
 2624    });
 2625}
 2626
 2627#[gpui::test]
 2628async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2629    init_test(cx, |_| {});
 2630
 2631    let mut cx = EditorTestContext::new(cx).await;
 2632
 2633    // For an empty selection, the preceding word fragment is deleted.
 2634    // For non-empty selections, only selected characters are deleted.
 2635    cx.set_state("onˇe two t«hreˇ»e four");
 2636    cx.update_editor(|editor, window, cx| {
 2637        editor.delete_to_previous_word_start(
 2638            &DeleteToPreviousWordStart {
 2639                ignore_newlines: false,
 2640                ignore_brackets: false,
 2641            },
 2642            window,
 2643            cx,
 2644        );
 2645    });
 2646    cx.assert_editor_state("ˇe two tˇe four");
 2647
 2648    cx.set_state("e tˇwo te «fˇ»our");
 2649    cx.update_editor(|editor, window, cx| {
 2650        editor.delete_to_next_word_end(
 2651            &DeleteToNextWordEnd {
 2652                ignore_newlines: false,
 2653                ignore_brackets: false,
 2654            },
 2655            window,
 2656            cx,
 2657        );
 2658    });
 2659    cx.assert_editor_state("e tˇ te ˇour");
 2660}
 2661
 2662#[gpui::test]
 2663async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2664    init_test(cx, |_| {});
 2665
 2666    let mut cx = EditorTestContext::new(cx).await;
 2667
 2668    cx.set_state("here is some text    ˇwith a space");
 2669    cx.update_editor(|editor, window, cx| {
 2670        editor.delete_to_previous_word_start(
 2671            &DeleteToPreviousWordStart {
 2672                ignore_newlines: false,
 2673                ignore_brackets: true,
 2674            },
 2675            window,
 2676            cx,
 2677        );
 2678    });
 2679    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2680    cx.assert_editor_state("here is some textˇwith a space");
 2681
 2682    cx.set_state("here is some text    ˇwith a space");
 2683    cx.update_editor(|editor, window, cx| {
 2684        editor.delete_to_previous_word_start(
 2685            &DeleteToPreviousWordStart {
 2686                ignore_newlines: false,
 2687                ignore_brackets: false,
 2688            },
 2689            window,
 2690            cx,
 2691        );
 2692    });
 2693    cx.assert_editor_state("here is some textˇwith a space");
 2694
 2695    cx.set_state("here is some textˇ    with a space");
 2696    cx.update_editor(|editor, window, cx| {
 2697        editor.delete_to_next_word_end(
 2698            &DeleteToNextWordEnd {
 2699                ignore_newlines: false,
 2700                ignore_brackets: true,
 2701            },
 2702            window,
 2703            cx,
 2704        );
 2705    });
 2706    // Same happens in the other direction.
 2707    cx.assert_editor_state("here is some textˇwith a space");
 2708
 2709    cx.set_state("here is some textˇ    with a space");
 2710    cx.update_editor(|editor, window, cx| {
 2711        editor.delete_to_next_word_end(
 2712            &DeleteToNextWordEnd {
 2713                ignore_newlines: false,
 2714                ignore_brackets: false,
 2715            },
 2716            window,
 2717            cx,
 2718        );
 2719    });
 2720    cx.assert_editor_state("here is some textˇwith a space");
 2721
 2722    cx.set_state("here is some textˇ    with a space");
 2723    cx.update_editor(|editor, window, cx| {
 2724        editor.delete_to_next_word_end(
 2725            &DeleteToNextWordEnd {
 2726                ignore_newlines: true,
 2727                ignore_brackets: false,
 2728            },
 2729            window,
 2730            cx,
 2731        );
 2732    });
 2733    cx.assert_editor_state("here is some textˇwith a space");
 2734    cx.update_editor(|editor, window, cx| {
 2735        editor.delete_to_previous_word_start(
 2736            &DeleteToPreviousWordStart {
 2737                ignore_newlines: true,
 2738                ignore_brackets: false,
 2739            },
 2740            window,
 2741            cx,
 2742        );
 2743    });
 2744    cx.assert_editor_state("here is some ˇwith a space");
 2745    cx.update_editor(|editor, window, cx| {
 2746        editor.delete_to_previous_word_start(
 2747            &DeleteToPreviousWordStart {
 2748                ignore_newlines: true,
 2749                ignore_brackets: false,
 2750            },
 2751            window,
 2752            cx,
 2753        );
 2754    });
 2755    // Single whitespaces are removed with the word behind them.
 2756    cx.assert_editor_state("here is ˇwith a space");
 2757    cx.update_editor(|editor, window, cx| {
 2758        editor.delete_to_previous_word_start(
 2759            &DeleteToPreviousWordStart {
 2760                ignore_newlines: true,
 2761                ignore_brackets: false,
 2762            },
 2763            window,
 2764            cx,
 2765        );
 2766    });
 2767    cx.assert_editor_state("here ˇwith a space");
 2768    cx.update_editor(|editor, window, cx| {
 2769        editor.delete_to_previous_word_start(
 2770            &DeleteToPreviousWordStart {
 2771                ignore_newlines: true,
 2772                ignore_brackets: false,
 2773            },
 2774            window,
 2775            cx,
 2776        );
 2777    });
 2778    cx.assert_editor_state("ˇwith a space");
 2779    cx.update_editor(|editor, window, cx| {
 2780        editor.delete_to_previous_word_start(
 2781            &DeleteToPreviousWordStart {
 2782                ignore_newlines: true,
 2783                ignore_brackets: false,
 2784            },
 2785            window,
 2786            cx,
 2787        );
 2788    });
 2789    cx.assert_editor_state("ˇwith a space");
 2790    cx.update_editor(|editor, window, cx| {
 2791        editor.delete_to_next_word_end(
 2792            &DeleteToNextWordEnd {
 2793                ignore_newlines: true,
 2794                ignore_brackets: false,
 2795            },
 2796            window,
 2797            cx,
 2798        );
 2799    });
 2800    // Same happens in the other direction.
 2801    cx.assert_editor_state("ˇ a space");
 2802    cx.update_editor(|editor, window, cx| {
 2803        editor.delete_to_next_word_end(
 2804            &DeleteToNextWordEnd {
 2805                ignore_newlines: true,
 2806                ignore_brackets: false,
 2807            },
 2808            window,
 2809            cx,
 2810        );
 2811    });
 2812    cx.assert_editor_state("ˇ space");
 2813    cx.update_editor(|editor, window, cx| {
 2814        editor.delete_to_next_word_end(
 2815            &DeleteToNextWordEnd {
 2816                ignore_newlines: true,
 2817                ignore_brackets: false,
 2818            },
 2819            window,
 2820            cx,
 2821        );
 2822    });
 2823    cx.assert_editor_state("ˇ");
 2824    cx.update_editor(|editor, window, cx| {
 2825        editor.delete_to_next_word_end(
 2826            &DeleteToNextWordEnd {
 2827                ignore_newlines: true,
 2828                ignore_brackets: false,
 2829            },
 2830            window,
 2831            cx,
 2832        );
 2833    });
 2834    cx.assert_editor_state("ˇ");
 2835    cx.update_editor(|editor, window, cx| {
 2836        editor.delete_to_previous_word_start(
 2837            &DeleteToPreviousWordStart {
 2838                ignore_newlines: true,
 2839                ignore_brackets: false,
 2840            },
 2841            window,
 2842            cx,
 2843        );
 2844    });
 2845    cx.assert_editor_state("ˇ");
 2846}
 2847
 2848#[gpui::test]
 2849async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2850    init_test(cx, |_| {});
 2851
 2852    let language = Arc::new(
 2853        Language::new(
 2854            LanguageConfig {
 2855                brackets: BracketPairConfig {
 2856                    pairs: vec![
 2857                        BracketPair {
 2858                            start: "\"".to_string(),
 2859                            end: "\"".to_string(),
 2860                            close: true,
 2861                            surround: true,
 2862                            newline: false,
 2863                        },
 2864                        BracketPair {
 2865                            start: "(".to_string(),
 2866                            end: ")".to_string(),
 2867                            close: true,
 2868                            surround: true,
 2869                            newline: true,
 2870                        },
 2871                    ],
 2872                    ..BracketPairConfig::default()
 2873                },
 2874                ..LanguageConfig::default()
 2875            },
 2876            Some(tree_sitter_rust::LANGUAGE.into()),
 2877        )
 2878        .with_brackets_query(
 2879            r#"
 2880                ("(" @open ")" @close)
 2881                ("\"" @open "\"" @close)
 2882            "#,
 2883        )
 2884        .unwrap(),
 2885    );
 2886
 2887    let mut cx = EditorTestContext::new(cx).await;
 2888    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2889
 2890    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2891    cx.update_editor(|editor, window, cx| {
 2892        editor.delete_to_previous_word_start(
 2893            &DeleteToPreviousWordStart {
 2894                ignore_newlines: true,
 2895                ignore_brackets: false,
 2896            },
 2897            window,
 2898            cx,
 2899        );
 2900    });
 2901    // Deletion stops before brackets if asked to not ignore them.
 2902    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2903    cx.update_editor(|editor, window, cx| {
 2904        editor.delete_to_previous_word_start(
 2905            &DeleteToPreviousWordStart {
 2906                ignore_newlines: true,
 2907                ignore_brackets: false,
 2908            },
 2909            window,
 2910            cx,
 2911        );
 2912    });
 2913    // Deletion has to remove a single bracket and then stop again.
 2914    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2915
 2916    cx.update_editor(|editor, window, cx| {
 2917        editor.delete_to_previous_word_start(
 2918            &DeleteToPreviousWordStart {
 2919                ignore_newlines: true,
 2920                ignore_brackets: false,
 2921            },
 2922            window,
 2923            cx,
 2924        );
 2925    });
 2926    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2927
 2928    cx.update_editor(|editor, window, cx| {
 2929        editor.delete_to_previous_word_start(
 2930            &DeleteToPreviousWordStart {
 2931                ignore_newlines: true,
 2932                ignore_brackets: false,
 2933            },
 2934            window,
 2935            cx,
 2936        );
 2937    });
 2938    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2939
 2940    cx.update_editor(|editor, window, cx| {
 2941        editor.delete_to_previous_word_start(
 2942            &DeleteToPreviousWordStart {
 2943                ignore_newlines: true,
 2944                ignore_brackets: false,
 2945            },
 2946            window,
 2947            cx,
 2948        );
 2949    });
 2950    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2951
 2952    cx.update_editor(|editor, window, cx| {
 2953        editor.delete_to_next_word_end(
 2954            &DeleteToNextWordEnd {
 2955                ignore_newlines: true,
 2956                ignore_brackets: false,
 2957            },
 2958            window,
 2959            cx,
 2960        );
 2961    });
 2962    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2963    cx.assert_editor_state(r#"ˇ");"#);
 2964
 2965    cx.update_editor(|editor, window, cx| {
 2966        editor.delete_to_next_word_end(
 2967            &DeleteToNextWordEnd {
 2968                ignore_newlines: true,
 2969                ignore_brackets: false,
 2970            },
 2971            window,
 2972            cx,
 2973        );
 2974    });
 2975    cx.assert_editor_state(r#"ˇ"#);
 2976
 2977    cx.update_editor(|editor, window, cx| {
 2978        editor.delete_to_next_word_end(
 2979            &DeleteToNextWordEnd {
 2980                ignore_newlines: true,
 2981                ignore_brackets: false,
 2982            },
 2983            window,
 2984            cx,
 2985        );
 2986    });
 2987    cx.assert_editor_state(r#"ˇ"#);
 2988
 2989    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2990    cx.update_editor(|editor, window, cx| {
 2991        editor.delete_to_previous_word_start(
 2992            &DeleteToPreviousWordStart {
 2993                ignore_newlines: true,
 2994                ignore_brackets: true,
 2995            },
 2996            window,
 2997            cx,
 2998        );
 2999    });
 3000    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 3001}
 3002
 3003#[gpui::test]
 3004fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 3005    init_test(cx, |_| {});
 3006
 3007    let editor = cx.add_window(|window, cx| {
 3008        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 3009        build_editor(buffer, window, cx)
 3010    });
 3011    let del_to_prev_word_start = DeleteToPreviousWordStart {
 3012        ignore_newlines: false,
 3013        ignore_brackets: false,
 3014    };
 3015    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 3016        ignore_newlines: true,
 3017        ignore_brackets: false,
 3018    };
 3019
 3020    _ = editor.update(cx, |editor, window, cx| {
 3021        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3022            s.select_display_ranges([
 3023                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 3024            ])
 3025        });
 3026        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3027        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 3028        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3029        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 3030        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3031        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 3032        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3033        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 3034        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3035        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3036        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3037        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3038    });
 3039}
 3040
 3041#[gpui::test]
 3042fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3043    init_test(cx, |_| {});
 3044
 3045    let editor = cx.add_window(|window, cx| {
 3046        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3047        build_editor(buffer, window, cx)
 3048    });
 3049    let del_to_next_word_end = DeleteToNextWordEnd {
 3050        ignore_newlines: false,
 3051        ignore_brackets: false,
 3052    };
 3053    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3054        ignore_newlines: true,
 3055        ignore_brackets: false,
 3056    };
 3057
 3058    _ = editor.update(cx, |editor, window, cx| {
 3059        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3060            s.select_display_ranges([
 3061                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3062            ])
 3063        });
 3064        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3065        assert_eq!(
 3066            editor.buffer.read(cx).read(cx).text(),
 3067            "one\n   two\nthree\n   four"
 3068        );
 3069        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3070        assert_eq!(
 3071            editor.buffer.read(cx).read(cx).text(),
 3072            "\n   two\nthree\n   four"
 3073        );
 3074        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3075        assert_eq!(
 3076            editor.buffer.read(cx).read(cx).text(),
 3077            "two\nthree\n   four"
 3078        );
 3079        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3080        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3081        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3082        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3083        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3084        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3085        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3086        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3087    });
 3088}
 3089
 3090#[gpui::test]
 3091fn test_newline(cx: &mut TestAppContext) {
 3092    init_test(cx, |_| {});
 3093
 3094    let editor = cx.add_window(|window, cx| {
 3095        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3096        build_editor(buffer, window, cx)
 3097    });
 3098
 3099    _ = editor.update(cx, |editor, window, cx| {
 3100        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3101            s.select_display_ranges([
 3102                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3103                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3104                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3105            ])
 3106        });
 3107
 3108        editor.newline(&Newline, window, cx);
 3109        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3110    });
 3111}
 3112
 3113#[gpui::test]
 3114fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3115    init_test(cx, |_| {});
 3116
 3117    let editor = cx.add_window(|window, cx| {
 3118        let buffer = MultiBuffer::build_simple(
 3119            "
 3120                a
 3121                b(
 3122                    X
 3123                )
 3124                c(
 3125                    X
 3126                )
 3127            "
 3128            .unindent()
 3129            .as_str(),
 3130            cx,
 3131        );
 3132        let mut editor = build_editor(buffer, window, cx);
 3133        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3134            s.select_ranges([
 3135                Point::new(2, 4)..Point::new(2, 5),
 3136                Point::new(5, 4)..Point::new(5, 5),
 3137            ])
 3138        });
 3139        editor
 3140    });
 3141
 3142    _ = editor.update(cx, |editor, window, cx| {
 3143        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3144        editor.buffer.update(cx, |buffer, cx| {
 3145            buffer.edit(
 3146                [
 3147                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3148                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3149                ],
 3150                None,
 3151                cx,
 3152            );
 3153            assert_eq!(
 3154                buffer.read(cx).text(),
 3155                "
 3156                    a
 3157                    b()
 3158                    c()
 3159                "
 3160                .unindent()
 3161            );
 3162        });
 3163        assert_eq!(
 3164            editor.selections.ranges(cx),
 3165            &[
 3166                Point::new(1, 2)..Point::new(1, 2),
 3167                Point::new(2, 2)..Point::new(2, 2),
 3168            ],
 3169        );
 3170
 3171        editor.newline(&Newline, window, cx);
 3172        assert_eq!(
 3173            editor.text(cx),
 3174            "
 3175                a
 3176                b(
 3177                )
 3178                c(
 3179                )
 3180            "
 3181            .unindent()
 3182        );
 3183
 3184        // The selections are moved after the inserted newlines
 3185        assert_eq!(
 3186            editor.selections.ranges(cx),
 3187            &[
 3188                Point::new(2, 0)..Point::new(2, 0),
 3189                Point::new(4, 0)..Point::new(4, 0),
 3190            ],
 3191        );
 3192    });
 3193}
 3194
 3195#[gpui::test]
 3196async fn test_newline_above(cx: &mut TestAppContext) {
 3197    init_test(cx, |settings| {
 3198        settings.defaults.tab_size = NonZeroU32::new(4)
 3199    });
 3200
 3201    let language = Arc::new(
 3202        Language::new(
 3203            LanguageConfig::default(),
 3204            Some(tree_sitter_rust::LANGUAGE.into()),
 3205        )
 3206        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3207        .unwrap(),
 3208    );
 3209
 3210    let mut cx = EditorTestContext::new(cx).await;
 3211    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3212    cx.set_state(indoc! {"
 3213        const a: ˇA = (
 3214 3215                «const_functionˇ»(ˇ),
 3216                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3217 3218        ˇ);ˇ
 3219    "});
 3220
 3221    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3222    cx.assert_editor_state(indoc! {"
 3223        ˇ
 3224        const a: A = (
 3225            ˇ
 3226            (
 3227                ˇ
 3228                ˇ
 3229                const_function(),
 3230                ˇ
 3231                ˇ
 3232                ˇ
 3233                ˇ
 3234                something_else,
 3235                ˇ
 3236            )
 3237            ˇ
 3238            ˇ
 3239        );
 3240    "});
 3241}
 3242
 3243#[gpui::test]
 3244async fn test_newline_below(cx: &mut TestAppContext) {
 3245    init_test(cx, |settings| {
 3246        settings.defaults.tab_size = NonZeroU32::new(4)
 3247    });
 3248
 3249    let language = Arc::new(
 3250        Language::new(
 3251            LanguageConfig::default(),
 3252            Some(tree_sitter_rust::LANGUAGE.into()),
 3253        )
 3254        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3255        .unwrap(),
 3256    );
 3257
 3258    let mut cx = EditorTestContext::new(cx).await;
 3259    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3260    cx.set_state(indoc! {"
 3261        const a: ˇA = (
 3262 3263                «const_functionˇ»(ˇ),
 3264                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3265 3266        ˇ);ˇ
 3267    "});
 3268
 3269    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3270    cx.assert_editor_state(indoc! {"
 3271        const a: A = (
 3272            ˇ
 3273            (
 3274                ˇ
 3275                const_function(),
 3276                ˇ
 3277                ˇ
 3278                something_else,
 3279                ˇ
 3280                ˇ
 3281                ˇ
 3282                ˇ
 3283            )
 3284            ˇ
 3285        );
 3286        ˇ
 3287        ˇ
 3288    "});
 3289}
 3290
 3291#[gpui::test]
 3292async fn test_newline_comments(cx: &mut TestAppContext) {
 3293    init_test(cx, |settings| {
 3294        settings.defaults.tab_size = NonZeroU32::new(4)
 3295    });
 3296
 3297    let language = Arc::new(Language::new(
 3298        LanguageConfig {
 3299            line_comments: vec!["// ".into()],
 3300            ..LanguageConfig::default()
 3301        },
 3302        None,
 3303    ));
 3304    {
 3305        let mut cx = EditorTestContext::new(cx).await;
 3306        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3307        cx.set_state(indoc! {"
 3308        // Fooˇ
 3309    "});
 3310
 3311        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3312        cx.assert_editor_state(indoc! {"
 3313        // Foo
 3314        // ˇ
 3315    "});
 3316        // Ensure that we add comment prefix when existing line contains space
 3317        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3318        cx.assert_editor_state(
 3319            indoc! {"
 3320        // Foo
 3321        //s
 3322        // ˇ
 3323    "}
 3324            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3325            .as_str(),
 3326        );
 3327        // Ensure that we add comment prefix when existing line does not contain space
 3328        cx.set_state(indoc! {"
 3329        // Foo
 3330        //ˇ
 3331    "});
 3332        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3333        cx.assert_editor_state(indoc! {"
 3334        // Foo
 3335        //
 3336        // ˇ
 3337    "});
 3338        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3339        cx.set_state(indoc! {"
 3340        ˇ// Foo
 3341    "});
 3342        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3343        cx.assert_editor_state(indoc! {"
 3344
 3345        ˇ// Foo
 3346    "});
 3347    }
 3348    // Ensure that comment continuations can be disabled.
 3349    update_test_language_settings(cx, |settings| {
 3350        settings.defaults.extend_comment_on_newline = Some(false);
 3351    });
 3352    let mut cx = EditorTestContext::new(cx).await;
 3353    cx.set_state(indoc! {"
 3354        // Fooˇ
 3355    "});
 3356    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3357    cx.assert_editor_state(indoc! {"
 3358        // Foo
 3359        ˇ
 3360    "});
 3361}
 3362
 3363#[gpui::test]
 3364async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3365    init_test(cx, |settings| {
 3366        settings.defaults.tab_size = NonZeroU32::new(4)
 3367    });
 3368
 3369    let language = Arc::new(Language::new(
 3370        LanguageConfig {
 3371            line_comments: vec!["// ".into(), "/// ".into()],
 3372            ..LanguageConfig::default()
 3373        },
 3374        None,
 3375    ));
 3376    {
 3377        let mut cx = EditorTestContext::new(cx).await;
 3378        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3379        cx.set_state(indoc! {"
 3380        //ˇ
 3381    "});
 3382        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3383        cx.assert_editor_state(indoc! {"
 3384        //
 3385        // ˇ
 3386    "});
 3387
 3388        cx.set_state(indoc! {"
 3389        ///ˇ
 3390    "});
 3391        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3392        cx.assert_editor_state(indoc! {"
 3393        ///
 3394        /// ˇ
 3395    "});
 3396    }
 3397}
 3398
 3399#[gpui::test]
 3400async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3401    init_test(cx, |settings| {
 3402        settings.defaults.tab_size = NonZeroU32::new(4)
 3403    });
 3404
 3405    let language = Arc::new(
 3406        Language::new(
 3407            LanguageConfig {
 3408                documentation_comment: Some(language::BlockCommentConfig {
 3409                    start: "/**".into(),
 3410                    end: "*/".into(),
 3411                    prefix: "* ".into(),
 3412                    tab_size: 1,
 3413                }),
 3414
 3415                ..LanguageConfig::default()
 3416            },
 3417            Some(tree_sitter_rust::LANGUAGE.into()),
 3418        )
 3419        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3420        .unwrap(),
 3421    );
 3422
 3423    {
 3424        let mut cx = EditorTestContext::new(cx).await;
 3425        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3426        cx.set_state(indoc! {"
 3427        /**ˇ
 3428    "});
 3429
 3430        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3431        cx.assert_editor_state(indoc! {"
 3432        /**
 3433         * ˇ
 3434    "});
 3435        // Ensure that if cursor is before the comment start,
 3436        // we do not actually insert a comment prefix.
 3437        cx.set_state(indoc! {"
 3438        ˇ/**
 3439    "});
 3440        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3441        cx.assert_editor_state(indoc! {"
 3442
 3443        ˇ/**
 3444    "});
 3445        // Ensure that if cursor is between it doesn't add comment prefix.
 3446        cx.set_state(indoc! {"
 3447        /*ˇ*
 3448    "});
 3449        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3450        cx.assert_editor_state(indoc! {"
 3451        /*
 3452        ˇ*
 3453    "});
 3454        // Ensure that if suffix exists on same line after cursor it adds new line.
 3455        cx.set_state(indoc! {"
 3456        /**ˇ*/
 3457    "});
 3458        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3459        cx.assert_editor_state(indoc! {"
 3460        /**
 3461         * ˇ
 3462         */
 3463    "});
 3464        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3465        cx.set_state(indoc! {"
 3466        /**ˇ */
 3467    "});
 3468        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3469        cx.assert_editor_state(indoc! {"
 3470        /**
 3471         * ˇ
 3472         */
 3473    "});
 3474        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3475        cx.set_state(indoc! {"
 3476        /** ˇ*/
 3477    "});
 3478        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3479        cx.assert_editor_state(
 3480            indoc! {"
 3481        /**s
 3482         * ˇ
 3483         */
 3484    "}
 3485            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3486            .as_str(),
 3487        );
 3488        // Ensure that delimiter space is preserved when newline on already
 3489        // spaced delimiter.
 3490        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3491        cx.assert_editor_state(
 3492            indoc! {"
 3493        /**s
 3494         *s
 3495         * ˇ
 3496         */
 3497    "}
 3498            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3499            .as_str(),
 3500        );
 3501        // Ensure that delimiter space is preserved when space is not
 3502        // on existing delimiter.
 3503        cx.set_state(indoc! {"
 3504        /**
 3505 3506         */
 3507    "});
 3508        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3509        cx.assert_editor_state(indoc! {"
 3510        /**
 3511         *
 3512         * ˇ
 3513         */
 3514    "});
 3515        // Ensure that if suffix exists on same line after cursor it
 3516        // doesn't add extra new line if prefix is not on same line.
 3517        cx.set_state(indoc! {"
 3518        /**
 3519        ˇ*/
 3520    "});
 3521        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3522        cx.assert_editor_state(indoc! {"
 3523        /**
 3524
 3525        ˇ*/
 3526    "});
 3527        // Ensure that it detects suffix after existing prefix.
 3528        cx.set_state(indoc! {"
 3529        /**ˇ/
 3530    "});
 3531        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3532        cx.assert_editor_state(indoc! {"
 3533        /**
 3534        ˇ/
 3535    "});
 3536        // Ensure that if suffix exists on same line before
 3537        // cursor it does not add comment prefix.
 3538        cx.set_state(indoc! {"
 3539        /** */ˇ
 3540    "});
 3541        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3542        cx.assert_editor_state(indoc! {"
 3543        /** */
 3544        ˇ
 3545    "});
 3546        // Ensure that if suffix exists on same line before
 3547        // cursor it does not add comment prefix.
 3548        cx.set_state(indoc! {"
 3549        /**
 3550         *
 3551         */ˇ
 3552    "});
 3553        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3554        cx.assert_editor_state(indoc! {"
 3555        /**
 3556         *
 3557         */
 3558         ˇ
 3559    "});
 3560
 3561        // Ensure that inline comment followed by code
 3562        // doesn't add comment prefix on newline
 3563        cx.set_state(indoc! {"
 3564        /** */ textˇ
 3565    "});
 3566        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3567        cx.assert_editor_state(indoc! {"
 3568        /** */ text
 3569        ˇ
 3570    "});
 3571
 3572        // Ensure that text after comment end tag
 3573        // doesn't add comment prefix on newline
 3574        cx.set_state(indoc! {"
 3575        /**
 3576         *
 3577         */ˇtext
 3578    "});
 3579        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3580        cx.assert_editor_state(indoc! {"
 3581        /**
 3582         *
 3583         */
 3584         ˇtext
 3585    "});
 3586
 3587        // Ensure if not comment block it doesn't
 3588        // add comment prefix on newline
 3589        cx.set_state(indoc! {"
 3590        * textˇ
 3591    "});
 3592        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3593        cx.assert_editor_state(indoc! {"
 3594        * text
 3595        ˇ
 3596    "});
 3597    }
 3598    // Ensure that comment continuations can be disabled.
 3599    update_test_language_settings(cx, |settings| {
 3600        settings.defaults.extend_comment_on_newline = Some(false);
 3601    });
 3602    let mut cx = EditorTestContext::new(cx).await;
 3603    cx.set_state(indoc! {"
 3604        /**ˇ
 3605    "});
 3606    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3607    cx.assert_editor_state(indoc! {"
 3608        /**
 3609        ˇ
 3610    "});
 3611}
 3612
 3613#[gpui::test]
 3614async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3615    init_test(cx, |settings| {
 3616        settings.defaults.tab_size = NonZeroU32::new(4)
 3617    });
 3618
 3619    let lua_language = Arc::new(Language::new(
 3620        LanguageConfig {
 3621            line_comments: vec!["--".into()],
 3622            block_comment: Some(language::BlockCommentConfig {
 3623                start: "--[[".into(),
 3624                prefix: "".into(),
 3625                end: "]]".into(),
 3626                tab_size: 0,
 3627            }),
 3628            ..LanguageConfig::default()
 3629        },
 3630        None,
 3631    ));
 3632
 3633    let mut cx = EditorTestContext::new(cx).await;
 3634    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3635
 3636    // Line with line comment should extend
 3637    cx.set_state(indoc! {"
 3638        --ˇ
 3639    "});
 3640    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3641    cx.assert_editor_state(indoc! {"
 3642        --
 3643        --ˇ
 3644    "});
 3645
 3646    // Line with block comment that matches line comment should not extend
 3647    cx.set_state(indoc! {"
 3648        --[[ˇ
 3649    "});
 3650    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3651    cx.assert_editor_state(indoc! {"
 3652        --[[
 3653        ˇ
 3654    "});
 3655}
 3656
 3657#[gpui::test]
 3658fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3659    init_test(cx, |_| {});
 3660
 3661    let editor = cx.add_window(|window, cx| {
 3662        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3663        let mut editor = build_editor(buffer, window, cx);
 3664        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3665            s.select_ranges([3..4, 11..12, 19..20])
 3666        });
 3667        editor
 3668    });
 3669
 3670    _ = editor.update(cx, |editor, window, cx| {
 3671        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3672        editor.buffer.update(cx, |buffer, cx| {
 3673            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3674            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3675        });
 3676        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3677
 3678        editor.insert("Z", window, cx);
 3679        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3680
 3681        // The selections are moved after the inserted characters
 3682        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3683    });
 3684}
 3685
 3686#[gpui::test]
 3687async fn test_tab(cx: &mut TestAppContext) {
 3688    init_test(cx, |settings| {
 3689        settings.defaults.tab_size = NonZeroU32::new(3)
 3690    });
 3691
 3692    let mut cx = EditorTestContext::new(cx).await;
 3693    cx.set_state(indoc! {"
 3694        ˇabˇc
 3695        ˇ🏀ˇ🏀ˇefg
 3696 3697    "});
 3698    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3699    cx.assert_editor_state(indoc! {"
 3700           ˇab ˇc
 3701           ˇ🏀  ˇ🏀  ˇefg
 3702        d  ˇ
 3703    "});
 3704
 3705    cx.set_state(indoc! {"
 3706        a
 3707        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3708    "});
 3709    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3710    cx.assert_editor_state(indoc! {"
 3711        a
 3712           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3713    "});
 3714}
 3715
 3716#[gpui::test]
 3717async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3718    init_test(cx, |_| {});
 3719
 3720    let mut cx = EditorTestContext::new(cx).await;
 3721    let language = Arc::new(
 3722        Language::new(
 3723            LanguageConfig::default(),
 3724            Some(tree_sitter_rust::LANGUAGE.into()),
 3725        )
 3726        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3727        .unwrap(),
 3728    );
 3729    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3730
 3731    // test when all cursors are not at suggested indent
 3732    // then simply move to their suggested indent location
 3733    cx.set_state(indoc! {"
 3734        const a: B = (
 3735            c(
 3736        ˇ
 3737        ˇ    )
 3738        );
 3739    "});
 3740    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3741    cx.assert_editor_state(indoc! {"
 3742        const a: B = (
 3743            c(
 3744                ˇ
 3745            ˇ)
 3746        );
 3747    "});
 3748
 3749    // test cursor already at suggested indent not moving when
 3750    // other cursors are yet to reach their suggested indents
 3751    cx.set_state(indoc! {"
 3752        ˇ
 3753        const a: B = (
 3754            c(
 3755                d(
 3756        ˇ
 3757                )
 3758        ˇ
 3759        ˇ    )
 3760        );
 3761    "});
 3762    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3763    cx.assert_editor_state(indoc! {"
 3764        ˇ
 3765        const a: B = (
 3766            c(
 3767                d(
 3768                    ˇ
 3769                )
 3770                ˇ
 3771            ˇ)
 3772        );
 3773    "});
 3774    // test when all cursors are at suggested indent then tab is inserted
 3775    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3776    cx.assert_editor_state(indoc! {"
 3777            ˇ
 3778        const a: B = (
 3779            c(
 3780                d(
 3781                        ˇ
 3782                )
 3783                    ˇ
 3784                ˇ)
 3785        );
 3786    "});
 3787
 3788    // test when current indent is less than suggested indent,
 3789    // we adjust line to match suggested indent and move cursor to it
 3790    //
 3791    // when no other cursor is at word boundary, all of them should move
 3792    cx.set_state(indoc! {"
 3793        const a: B = (
 3794            c(
 3795                d(
 3796        ˇ
 3797        ˇ   )
 3798        ˇ   )
 3799        );
 3800    "});
 3801    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3802    cx.assert_editor_state(indoc! {"
 3803        const a: B = (
 3804            c(
 3805                d(
 3806                    ˇ
 3807                ˇ)
 3808            ˇ)
 3809        );
 3810    "});
 3811
 3812    // test when current indent is less than suggested indent,
 3813    // we adjust line to match suggested indent and move cursor to it
 3814    //
 3815    // when some other cursor is at word boundary, it should not move
 3816    cx.set_state(indoc! {"
 3817        const a: B = (
 3818            c(
 3819                d(
 3820        ˇ
 3821        ˇ   )
 3822           ˇ)
 3823        );
 3824    "});
 3825    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3826    cx.assert_editor_state(indoc! {"
 3827        const a: B = (
 3828            c(
 3829                d(
 3830                    ˇ
 3831                ˇ)
 3832            ˇ)
 3833        );
 3834    "});
 3835
 3836    // test when current indent is more than suggested indent,
 3837    // we just move cursor to current indent instead of suggested indent
 3838    //
 3839    // when no other cursor is at word boundary, all of them should move
 3840    cx.set_state(indoc! {"
 3841        const a: B = (
 3842            c(
 3843                d(
 3844        ˇ
 3845        ˇ                )
 3846        ˇ   )
 3847        );
 3848    "});
 3849    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3850    cx.assert_editor_state(indoc! {"
 3851        const a: B = (
 3852            c(
 3853                d(
 3854                    ˇ
 3855                        ˇ)
 3856            ˇ)
 3857        );
 3858    "});
 3859    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3860    cx.assert_editor_state(indoc! {"
 3861        const a: B = (
 3862            c(
 3863                d(
 3864                        ˇ
 3865                            ˇ)
 3866                ˇ)
 3867        );
 3868    "});
 3869
 3870    // test when current indent is more than suggested indent,
 3871    // we just move cursor to current indent instead of suggested indent
 3872    //
 3873    // when some other cursor is at word boundary, it doesn't move
 3874    cx.set_state(indoc! {"
 3875        const a: B = (
 3876            c(
 3877                d(
 3878        ˇ
 3879        ˇ                )
 3880            ˇ)
 3881        );
 3882    "});
 3883    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3884    cx.assert_editor_state(indoc! {"
 3885        const a: B = (
 3886            c(
 3887                d(
 3888                    ˇ
 3889                        ˇ)
 3890            ˇ)
 3891        );
 3892    "});
 3893
 3894    // handle auto-indent when there are multiple cursors on the same line
 3895    cx.set_state(indoc! {"
 3896        const a: B = (
 3897            c(
 3898        ˇ    ˇ
 3899        ˇ    )
 3900        );
 3901    "});
 3902    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3903    cx.assert_editor_state(indoc! {"
 3904        const a: B = (
 3905            c(
 3906                ˇ
 3907            ˇ)
 3908        );
 3909    "});
 3910}
 3911
 3912#[gpui::test]
 3913async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3914    init_test(cx, |settings| {
 3915        settings.defaults.tab_size = NonZeroU32::new(3)
 3916    });
 3917
 3918    let mut cx = EditorTestContext::new(cx).await;
 3919    cx.set_state(indoc! {"
 3920         ˇ
 3921        \t ˇ
 3922        \t  ˇ
 3923        \t   ˇ
 3924         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3925    "});
 3926
 3927    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3928    cx.assert_editor_state(indoc! {"
 3929           ˇ
 3930        \t   ˇ
 3931        \t   ˇ
 3932        \t      ˇ
 3933         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3934    "});
 3935}
 3936
 3937#[gpui::test]
 3938async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3939    init_test(cx, |settings| {
 3940        settings.defaults.tab_size = NonZeroU32::new(4)
 3941    });
 3942
 3943    let language = Arc::new(
 3944        Language::new(
 3945            LanguageConfig::default(),
 3946            Some(tree_sitter_rust::LANGUAGE.into()),
 3947        )
 3948        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3949        .unwrap(),
 3950    );
 3951
 3952    let mut cx = EditorTestContext::new(cx).await;
 3953    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3954    cx.set_state(indoc! {"
 3955        fn a() {
 3956            if b {
 3957        \t ˇc
 3958            }
 3959        }
 3960    "});
 3961
 3962    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3963    cx.assert_editor_state(indoc! {"
 3964        fn a() {
 3965            if b {
 3966                ˇc
 3967            }
 3968        }
 3969    "});
 3970}
 3971
 3972#[gpui::test]
 3973async fn test_indent_outdent(cx: &mut TestAppContext) {
 3974    init_test(cx, |settings| {
 3975        settings.defaults.tab_size = NonZeroU32::new(4);
 3976    });
 3977
 3978    let mut cx = EditorTestContext::new(cx).await;
 3979
 3980    cx.set_state(indoc! {"
 3981          «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            «oneˇ» «twoˇ»
 3988        three
 3989         four
 3990    "});
 3991
 3992    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3993    cx.assert_editor_state(indoc! {"
 3994        «oneˇ» «twoˇ»
 3995        three
 3996         four
 3997    "});
 3998
 3999    // select across line ending
 4000    cx.set_state(indoc! {"
 4001        one two
 4002        t«hree
 4003        ˇ» four
 4004    "});
 4005    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4006    cx.assert_editor_state(indoc! {"
 4007        one two
 4008            t«hree
 4009        ˇ» four
 4010    "});
 4011
 4012    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4013    cx.assert_editor_state(indoc! {"
 4014        one two
 4015        t«hree
 4016        ˇ» four
 4017    "});
 4018
 4019    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4020    cx.set_state(indoc! {"
 4021        one two
 4022        ˇthree
 4023            four
 4024    "});
 4025    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4026    cx.assert_editor_state(indoc! {"
 4027        one two
 4028            ˇthree
 4029            four
 4030    "});
 4031
 4032    cx.set_state(indoc! {"
 4033        one two
 4034        ˇ    three
 4035            four
 4036    "});
 4037    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4038    cx.assert_editor_state(indoc! {"
 4039        one two
 4040        ˇthree
 4041            four
 4042    "});
 4043}
 4044
 4045#[gpui::test]
 4046async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4047    // This is a regression test for issue #33761
 4048    init_test(cx, |_| {});
 4049
 4050    let mut cx = EditorTestContext::new(cx).await;
 4051    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4052    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4053
 4054    cx.set_state(
 4055        r#"ˇ#     ingress:
 4056ˇ#         api:
 4057ˇ#             enabled: false
 4058ˇ#             pathType: Prefix
 4059ˇ#           console:
 4060ˇ#               enabled: false
 4061ˇ#               pathType: Prefix
 4062"#,
 4063    );
 4064
 4065    // Press tab to indent all lines
 4066    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4067
 4068    cx.assert_editor_state(
 4069        r#"    ˇ#     ingress:
 4070    ˇ#         api:
 4071    ˇ#             enabled: false
 4072    ˇ#             pathType: Prefix
 4073    ˇ#           console:
 4074    ˇ#               enabled: false
 4075    ˇ#               pathType: Prefix
 4076"#,
 4077    );
 4078}
 4079
 4080#[gpui::test]
 4081async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4082    // This is a test to make sure our fix for issue #33761 didn't break anything
 4083    init_test(cx, |_| {});
 4084
 4085    let mut cx = EditorTestContext::new(cx).await;
 4086    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4087    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4088
 4089    cx.set_state(
 4090        r#"ˇingress:
 4091ˇ  api:
 4092ˇ    enabled: false
 4093ˇ    pathType: Prefix
 4094"#,
 4095    );
 4096
 4097    // Press tab to indent all lines
 4098    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4099
 4100    cx.assert_editor_state(
 4101        r#"ˇingress:
 4102    ˇapi:
 4103        ˇenabled: false
 4104        ˇpathType: Prefix
 4105"#,
 4106    );
 4107}
 4108
 4109#[gpui::test]
 4110async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4111    init_test(cx, |settings| {
 4112        settings.defaults.hard_tabs = Some(true);
 4113    });
 4114
 4115    let mut cx = EditorTestContext::new(cx).await;
 4116
 4117    // select two ranges on one line
 4118    cx.set_state(indoc! {"
 4119        «oneˇ» «twoˇ»
 4120        three
 4121        four
 4122    "});
 4123    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4124    cx.assert_editor_state(indoc! {"
 4125        \t«oneˇ» «twoˇ»
 4126        three
 4127        four
 4128    "});
 4129    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4130    cx.assert_editor_state(indoc! {"
 4131        \t\t«oneˇ» «twoˇ»
 4132        three
 4133        four
 4134    "});
 4135    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4136    cx.assert_editor_state(indoc! {"
 4137        \t«oneˇ» «twoˇ»
 4138        three
 4139        four
 4140    "});
 4141    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4142    cx.assert_editor_state(indoc! {"
 4143        «oneˇ» «twoˇ»
 4144        three
 4145        four
 4146    "});
 4147
 4148    // select across a line ending
 4149    cx.set_state(indoc! {"
 4150        one two
 4151        t«hree
 4152        ˇ»four
 4153    "});
 4154    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4155    cx.assert_editor_state(indoc! {"
 4156        one two
 4157        \tt«hree
 4158        ˇ»four
 4159    "});
 4160    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4161    cx.assert_editor_state(indoc! {"
 4162        one two
 4163        \t\tt«hree
 4164        ˇ»four
 4165    "});
 4166    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4167    cx.assert_editor_state(indoc! {"
 4168        one two
 4169        \tt«hree
 4170        ˇ»four
 4171    "});
 4172    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4173    cx.assert_editor_state(indoc! {"
 4174        one two
 4175        t«hree
 4176        ˇ»four
 4177    "});
 4178
 4179    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4180    cx.set_state(indoc! {"
 4181        one two
 4182        ˇthree
 4183        four
 4184    "});
 4185    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4186    cx.assert_editor_state(indoc! {"
 4187        one two
 4188        ˇthree
 4189        four
 4190    "});
 4191    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4192    cx.assert_editor_state(indoc! {"
 4193        one two
 4194        \tˇthree
 4195        four
 4196    "});
 4197    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4198    cx.assert_editor_state(indoc! {"
 4199        one two
 4200        ˇthree
 4201        four
 4202    "});
 4203}
 4204
 4205#[gpui::test]
 4206fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4207    init_test(cx, |settings| {
 4208        settings.languages.0.extend([
 4209            (
 4210                "TOML".into(),
 4211                LanguageSettingsContent {
 4212                    tab_size: NonZeroU32::new(2),
 4213                    ..Default::default()
 4214                },
 4215            ),
 4216            (
 4217                "Rust".into(),
 4218                LanguageSettingsContent {
 4219                    tab_size: NonZeroU32::new(4),
 4220                    ..Default::default()
 4221                },
 4222            ),
 4223        ]);
 4224    });
 4225
 4226    let toml_language = Arc::new(Language::new(
 4227        LanguageConfig {
 4228            name: "TOML".into(),
 4229            ..Default::default()
 4230        },
 4231        None,
 4232    ));
 4233    let rust_language = Arc::new(Language::new(
 4234        LanguageConfig {
 4235            name: "Rust".into(),
 4236            ..Default::default()
 4237        },
 4238        None,
 4239    ));
 4240
 4241    let toml_buffer =
 4242        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4243    let rust_buffer =
 4244        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4245    let multibuffer = cx.new(|cx| {
 4246        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4247        multibuffer.push_excerpts(
 4248            toml_buffer.clone(),
 4249            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4250            cx,
 4251        );
 4252        multibuffer.push_excerpts(
 4253            rust_buffer.clone(),
 4254            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4255            cx,
 4256        );
 4257        multibuffer
 4258    });
 4259
 4260    cx.add_window(|window, cx| {
 4261        let mut editor = build_editor(multibuffer, window, cx);
 4262
 4263        assert_eq!(
 4264            editor.text(cx),
 4265            indoc! {"
 4266                a = 1
 4267                b = 2
 4268
 4269                const c: usize = 3;
 4270            "}
 4271        );
 4272
 4273        select_ranges(
 4274            &mut editor,
 4275            indoc! {"
 4276                «aˇ» = 1
 4277                b = 2
 4278
 4279                «const c:ˇ» usize = 3;
 4280            "},
 4281            window,
 4282            cx,
 4283        );
 4284
 4285        editor.tab(&Tab, window, cx);
 4286        assert_text_with_selections(
 4287            &mut editor,
 4288            indoc! {"
 4289                  «aˇ» = 1
 4290                b = 2
 4291
 4292                    «const c:ˇ» usize = 3;
 4293            "},
 4294            cx,
 4295        );
 4296        editor.backtab(&Backtab, window, cx);
 4297        assert_text_with_selections(
 4298            &mut editor,
 4299            indoc! {"
 4300                «aˇ» = 1
 4301                b = 2
 4302
 4303                «const c:ˇ» usize = 3;
 4304            "},
 4305            cx,
 4306        );
 4307
 4308        editor
 4309    });
 4310}
 4311
 4312#[gpui::test]
 4313async fn test_backspace(cx: &mut TestAppContext) {
 4314    init_test(cx, |_| {});
 4315
 4316    let mut cx = EditorTestContext::new(cx).await;
 4317
 4318    // Basic backspace
 4319    cx.set_state(indoc! {"
 4320        onˇe two three
 4321        fou«rˇ» five six
 4322        seven «ˇeight nine
 4323        »ten
 4324    "});
 4325    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4326    cx.assert_editor_state(indoc! {"
 4327        oˇe two three
 4328        fouˇ five six
 4329        seven ˇten
 4330    "});
 4331
 4332    // Test backspace inside and around indents
 4333    cx.set_state(indoc! {"
 4334        zero
 4335            ˇone
 4336                ˇtwo
 4337            ˇ ˇ ˇ  three
 4338        ˇ  ˇ  four
 4339    "});
 4340    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4341    cx.assert_editor_state(indoc! {"
 4342        zero
 4343        ˇone
 4344            ˇtwo
 4345        ˇ  threeˇ  four
 4346    "});
 4347}
 4348
 4349#[gpui::test]
 4350async fn test_delete(cx: &mut TestAppContext) {
 4351    init_test(cx, |_| {});
 4352
 4353    let mut cx = EditorTestContext::new(cx).await;
 4354    cx.set_state(indoc! {"
 4355        onˇe two three
 4356        fou«rˇ» five six
 4357        seven «ˇeight nine
 4358        »ten
 4359    "});
 4360    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4361    cx.assert_editor_state(indoc! {"
 4362        onˇ two three
 4363        fouˇ five six
 4364        seven ˇten
 4365    "});
 4366}
 4367
 4368#[gpui::test]
 4369fn test_delete_line(cx: &mut TestAppContext) {
 4370    init_test(cx, |_| {});
 4371
 4372    let editor = cx.add_window(|window, cx| {
 4373        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4374        build_editor(buffer, window, cx)
 4375    });
 4376    _ = editor.update(cx, |editor, window, cx| {
 4377        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4378            s.select_display_ranges([
 4379                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4380                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4381                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4382            ])
 4383        });
 4384        editor.delete_line(&DeleteLine, window, cx);
 4385        assert_eq!(editor.display_text(cx), "ghi");
 4386        assert_eq!(
 4387            editor.selections.display_ranges(cx),
 4388            vec![
 4389                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4390                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4391            ]
 4392        );
 4393    });
 4394
 4395    let editor = cx.add_window(|window, cx| {
 4396        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4397        build_editor(buffer, window, cx)
 4398    });
 4399    _ = editor.update(cx, |editor, window, cx| {
 4400        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4401            s.select_display_ranges([
 4402                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4403            ])
 4404        });
 4405        editor.delete_line(&DeleteLine, window, cx);
 4406        assert_eq!(editor.display_text(cx), "ghi\n");
 4407        assert_eq!(
 4408            editor.selections.display_ranges(cx),
 4409            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4410        );
 4411    });
 4412
 4413    let editor = cx.add_window(|window, cx| {
 4414        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4415        build_editor(buffer, window, cx)
 4416    });
 4417    _ = editor.update(cx, |editor, window, cx| {
 4418        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4419            s.select_display_ranges([
 4420                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4421            ])
 4422        });
 4423        editor.delete_line(&DeleteLine, window, cx);
 4424        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4425        assert_eq!(
 4426            editor.selections.display_ranges(cx),
 4427            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4428        );
 4429    });
 4430}
 4431
 4432#[gpui::test]
 4433fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4434    init_test(cx, |_| {});
 4435
 4436    cx.add_window(|window, cx| {
 4437        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4438        let mut editor = build_editor(buffer.clone(), window, cx);
 4439        let buffer = buffer.read(cx).as_singleton().unwrap();
 4440
 4441        assert_eq!(
 4442            editor.selections.ranges::<Point>(cx),
 4443            &[Point::new(0, 0)..Point::new(0, 0)]
 4444        );
 4445
 4446        // When on single line, replace newline at end by space
 4447        editor.join_lines(&JoinLines, window, cx);
 4448        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4449        assert_eq!(
 4450            editor.selections.ranges::<Point>(cx),
 4451            &[Point::new(0, 3)..Point::new(0, 3)]
 4452        );
 4453
 4454        // When multiple lines are selected, remove newlines that are spanned by the selection
 4455        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4456            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4457        });
 4458        editor.join_lines(&JoinLines, window, cx);
 4459        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4460        assert_eq!(
 4461            editor.selections.ranges::<Point>(cx),
 4462            &[Point::new(0, 11)..Point::new(0, 11)]
 4463        );
 4464
 4465        // Undo should be transactional
 4466        editor.undo(&Undo, window, cx);
 4467        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4468        assert_eq!(
 4469            editor.selections.ranges::<Point>(cx),
 4470            &[Point::new(0, 5)..Point::new(2, 2)]
 4471        );
 4472
 4473        // When joining an empty line don't insert a space
 4474        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4475            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4476        });
 4477        editor.join_lines(&JoinLines, window, cx);
 4478        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4479        assert_eq!(
 4480            editor.selections.ranges::<Point>(cx),
 4481            [Point::new(2, 3)..Point::new(2, 3)]
 4482        );
 4483
 4484        // We can remove trailing newlines
 4485        editor.join_lines(&JoinLines, window, cx);
 4486        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4487        assert_eq!(
 4488            editor.selections.ranges::<Point>(cx),
 4489            [Point::new(2, 3)..Point::new(2, 3)]
 4490        );
 4491
 4492        // We don't blow up on the last line
 4493        editor.join_lines(&JoinLines, window, cx);
 4494        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4495        assert_eq!(
 4496            editor.selections.ranges::<Point>(cx),
 4497            [Point::new(2, 3)..Point::new(2, 3)]
 4498        );
 4499
 4500        // reset to test indentation
 4501        editor.buffer.update(cx, |buffer, cx| {
 4502            buffer.edit(
 4503                [
 4504                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4505                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4506                ],
 4507                None,
 4508                cx,
 4509            )
 4510        });
 4511
 4512        // We remove any leading spaces
 4513        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4514        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4515            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4516        });
 4517        editor.join_lines(&JoinLines, window, cx);
 4518        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4519
 4520        // We don't insert a space for a line containing only spaces
 4521        editor.join_lines(&JoinLines, window, cx);
 4522        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4523
 4524        // We ignore any leading tabs
 4525        editor.join_lines(&JoinLines, window, cx);
 4526        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4527
 4528        editor
 4529    });
 4530}
 4531
 4532#[gpui::test]
 4533fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4534    init_test(cx, |_| {});
 4535
 4536    cx.add_window(|window, cx| {
 4537        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4538        let mut editor = build_editor(buffer.clone(), window, cx);
 4539        let buffer = buffer.read(cx).as_singleton().unwrap();
 4540
 4541        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4542            s.select_ranges([
 4543                Point::new(0, 2)..Point::new(1, 1),
 4544                Point::new(1, 2)..Point::new(1, 2),
 4545                Point::new(3, 1)..Point::new(3, 2),
 4546            ])
 4547        });
 4548
 4549        editor.join_lines(&JoinLines, window, cx);
 4550        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4551
 4552        assert_eq!(
 4553            editor.selections.ranges::<Point>(cx),
 4554            [
 4555                Point::new(0, 7)..Point::new(0, 7),
 4556                Point::new(1, 3)..Point::new(1, 3)
 4557            ]
 4558        );
 4559        editor
 4560    });
 4561}
 4562
 4563#[gpui::test]
 4564async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4565    init_test(cx, |_| {});
 4566
 4567    let mut cx = EditorTestContext::new(cx).await;
 4568
 4569    let diff_base = r#"
 4570        Line 0
 4571        Line 1
 4572        Line 2
 4573        Line 3
 4574        "#
 4575    .unindent();
 4576
 4577    cx.set_state(
 4578        &r#"
 4579        ˇLine 0
 4580        Line 1
 4581        Line 2
 4582        Line 3
 4583        "#
 4584        .unindent(),
 4585    );
 4586
 4587    cx.set_head_text(&diff_base);
 4588    executor.run_until_parked();
 4589
 4590    // Join lines
 4591    cx.update_editor(|editor, window, cx| {
 4592        editor.join_lines(&JoinLines, window, cx);
 4593    });
 4594    executor.run_until_parked();
 4595
 4596    cx.assert_editor_state(
 4597        &r#"
 4598        Line 0ˇ Line 1
 4599        Line 2
 4600        Line 3
 4601        "#
 4602        .unindent(),
 4603    );
 4604    // Join again
 4605    cx.update_editor(|editor, window, cx| {
 4606        editor.join_lines(&JoinLines, window, cx);
 4607    });
 4608    executor.run_until_parked();
 4609
 4610    cx.assert_editor_state(
 4611        &r#"
 4612        Line 0 Line 1ˇ Line 2
 4613        Line 3
 4614        "#
 4615        .unindent(),
 4616    );
 4617}
 4618
 4619#[gpui::test]
 4620async fn test_custom_newlines_cause_no_false_positive_diffs(
 4621    executor: BackgroundExecutor,
 4622    cx: &mut TestAppContext,
 4623) {
 4624    init_test(cx, |_| {});
 4625    let mut cx = EditorTestContext::new(cx).await;
 4626    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4627    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4628    executor.run_until_parked();
 4629
 4630    cx.update_editor(|editor, window, cx| {
 4631        let snapshot = editor.snapshot(window, cx);
 4632        assert_eq!(
 4633            snapshot
 4634                .buffer_snapshot()
 4635                .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
 4636                .collect::<Vec<_>>(),
 4637            Vec::new(),
 4638            "Should not have any diffs for files with custom newlines"
 4639        );
 4640    });
 4641}
 4642
 4643#[gpui::test]
 4644async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4645    init_test(cx, |_| {});
 4646
 4647    let mut cx = EditorTestContext::new(cx).await;
 4648
 4649    // Test sort_lines_case_insensitive()
 4650    cx.set_state(indoc! {"
 4651        «z
 4652        y
 4653        x
 4654        Z
 4655        Y
 4656        Xˇ»
 4657    "});
 4658    cx.update_editor(|e, window, cx| {
 4659        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4660    });
 4661    cx.assert_editor_state(indoc! {"
 4662        «x
 4663        X
 4664        y
 4665        Y
 4666        z
 4667        Zˇ»
 4668    "});
 4669
 4670    // Test sort_lines_by_length()
 4671    //
 4672    // Demonstrates:
 4673    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4674    // - sort is stable
 4675    cx.set_state(indoc! {"
 4676        «123
 4677        æ
 4678        12
 4679 4680        1
 4681        æˇ»
 4682    "});
 4683    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4684    cx.assert_editor_state(indoc! {"
 4685        «æ
 4686 4687        1
 4688        æ
 4689        12
 4690        123ˇ»
 4691    "});
 4692
 4693    // Test reverse_lines()
 4694    cx.set_state(indoc! {"
 4695        «5
 4696        4
 4697        3
 4698        2
 4699        1ˇ»
 4700    "});
 4701    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4702    cx.assert_editor_state(indoc! {"
 4703        «1
 4704        2
 4705        3
 4706        4
 4707        5ˇ»
 4708    "});
 4709
 4710    // Skip testing shuffle_line()
 4711
 4712    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4713    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4714
 4715    // Don't manipulate when cursor is on single line, but expand the selection
 4716    cx.set_state(indoc! {"
 4717        ddˇdd
 4718        ccc
 4719        bb
 4720        a
 4721    "});
 4722    cx.update_editor(|e, window, cx| {
 4723        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4724    });
 4725    cx.assert_editor_state(indoc! {"
 4726        «ddddˇ»
 4727        ccc
 4728        bb
 4729        a
 4730    "});
 4731
 4732    // Basic manipulate case
 4733    // Start selection moves to column 0
 4734    // End of selection shrinks to fit shorter line
 4735    cx.set_state(indoc! {"
 4736        dd«d
 4737        ccc
 4738        bb
 4739        aaaaaˇ»
 4740    "});
 4741    cx.update_editor(|e, window, cx| {
 4742        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4743    });
 4744    cx.assert_editor_state(indoc! {"
 4745        «aaaaa
 4746        bb
 4747        ccc
 4748        dddˇ»
 4749    "});
 4750
 4751    // Manipulate case with newlines
 4752    cx.set_state(indoc! {"
 4753        dd«d
 4754        ccc
 4755
 4756        bb
 4757        aaaaa
 4758
 4759        ˇ»
 4760    "});
 4761    cx.update_editor(|e, window, cx| {
 4762        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4763    });
 4764    cx.assert_editor_state(indoc! {"
 4765        «
 4766
 4767        aaaaa
 4768        bb
 4769        ccc
 4770        dddˇ»
 4771
 4772    "});
 4773
 4774    // Adding new line
 4775    cx.set_state(indoc! {"
 4776        aa«a
 4777        bbˇ»b
 4778    "});
 4779    cx.update_editor(|e, window, cx| {
 4780        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4781    });
 4782    cx.assert_editor_state(indoc! {"
 4783        «aaa
 4784        bbb
 4785        added_lineˇ»
 4786    "});
 4787
 4788    // Removing line
 4789    cx.set_state(indoc! {"
 4790        aa«a
 4791        bbbˇ»
 4792    "});
 4793    cx.update_editor(|e, window, cx| {
 4794        e.manipulate_immutable_lines(window, cx, |lines| {
 4795            lines.pop();
 4796        })
 4797    });
 4798    cx.assert_editor_state(indoc! {"
 4799        «aaaˇ»
 4800    "});
 4801
 4802    // Removing all lines
 4803    cx.set_state(indoc! {"
 4804        aa«a
 4805        bbbˇ»
 4806    "});
 4807    cx.update_editor(|e, window, cx| {
 4808        e.manipulate_immutable_lines(window, cx, |lines| {
 4809            lines.drain(..);
 4810        })
 4811    });
 4812    cx.assert_editor_state(indoc! {"
 4813        ˇ
 4814    "});
 4815}
 4816
 4817#[gpui::test]
 4818async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4819    init_test(cx, |_| {});
 4820
 4821    let mut cx = EditorTestContext::new(cx).await;
 4822
 4823    // Consider continuous selection as single selection
 4824    cx.set_state(indoc! {"
 4825        Aaa«aa
 4826        cˇ»c«c
 4827        bb
 4828        aaaˇ»aa
 4829    "});
 4830    cx.update_editor(|e, window, cx| {
 4831        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4832    });
 4833    cx.assert_editor_state(indoc! {"
 4834        «Aaaaa
 4835        ccc
 4836        bb
 4837        aaaaaˇ»
 4838    "});
 4839
 4840    cx.set_state(indoc! {"
 4841        Aaa«aa
 4842        cˇ»c«c
 4843        bb
 4844        aaaˇ»aa
 4845    "});
 4846    cx.update_editor(|e, window, cx| {
 4847        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4848    });
 4849    cx.assert_editor_state(indoc! {"
 4850        «Aaaaa
 4851        ccc
 4852        bbˇ»
 4853    "});
 4854
 4855    // Consider non continuous selection as distinct dedup operations
 4856    cx.set_state(indoc! {"
 4857        «aaaaa
 4858        bb
 4859        aaaaa
 4860        aaaaaˇ»
 4861
 4862        aaa«aaˇ»
 4863    "});
 4864    cx.update_editor(|e, window, cx| {
 4865        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4866    });
 4867    cx.assert_editor_state(indoc! {"
 4868        «aaaaa
 4869        bbˇ»
 4870
 4871        «aaaaaˇ»
 4872    "});
 4873}
 4874
 4875#[gpui::test]
 4876async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4877    init_test(cx, |_| {});
 4878
 4879    let mut cx = EditorTestContext::new(cx).await;
 4880
 4881    cx.set_state(indoc! {"
 4882        «Aaa
 4883        aAa
 4884        Aaaˇ»
 4885    "});
 4886    cx.update_editor(|e, window, cx| {
 4887        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4888    });
 4889    cx.assert_editor_state(indoc! {"
 4890        «Aaa
 4891        aAaˇ»
 4892    "});
 4893
 4894    cx.set_state(indoc! {"
 4895        «Aaa
 4896        aAa
 4897        aaAˇ»
 4898    "});
 4899    cx.update_editor(|e, window, cx| {
 4900        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4901    });
 4902    cx.assert_editor_state(indoc! {"
 4903        «Aaaˇ»
 4904    "});
 4905}
 4906
 4907#[gpui::test]
 4908async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 4909    init_test(cx, |_| {});
 4910
 4911    let mut cx = EditorTestContext::new(cx).await;
 4912
 4913    let js_language = Arc::new(Language::new(
 4914        LanguageConfig {
 4915            name: "JavaScript".into(),
 4916            wrap_characters: Some(language::WrapCharactersConfig {
 4917                start_prefix: "<".into(),
 4918                start_suffix: ">".into(),
 4919                end_prefix: "</".into(),
 4920                end_suffix: ">".into(),
 4921            }),
 4922            ..LanguageConfig::default()
 4923        },
 4924        None,
 4925    ));
 4926
 4927    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4928
 4929    cx.set_state(indoc! {"
 4930        «testˇ»
 4931    "});
 4932    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4933    cx.assert_editor_state(indoc! {"
 4934        <«ˇ»>test</«ˇ»>
 4935    "});
 4936
 4937    cx.set_state(indoc! {"
 4938        «test
 4939         testˇ»
 4940    "});
 4941    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4942    cx.assert_editor_state(indoc! {"
 4943        <«ˇ»>test
 4944         test</«ˇ»>
 4945    "});
 4946
 4947    cx.set_state(indoc! {"
 4948        teˇst
 4949    "});
 4950    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4951    cx.assert_editor_state(indoc! {"
 4952        te<«ˇ»></«ˇ»>st
 4953    "});
 4954}
 4955
 4956#[gpui::test]
 4957async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 4958    init_test(cx, |_| {});
 4959
 4960    let mut cx = EditorTestContext::new(cx).await;
 4961
 4962    let js_language = Arc::new(Language::new(
 4963        LanguageConfig {
 4964            name: "JavaScript".into(),
 4965            wrap_characters: Some(language::WrapCharactersConfig {
 4966                start_prefix: "<".into(),
 4967                start_suffix: ">".into(),
 4968                end_prefix: "</".into(),
 4969                end_suffix: ">".into(),
 4970            }),
 4971            ..LanguageConfig::default()
 4972        },
 4973        None,
 4974    ));
 4975
 4976    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4977
 4978    cx.set_state(indoc! {"
 4979        «testˇ»
 4980        «testˇ» «testˇ»
 4981        «testˇ»
 4982    "});
 4983    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4984    cx.assert_editor_state(indoc! {"
 4985        <«ˇ»>test</«ˇ»>
 4986        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 4987        <«ˇ»>test</«ˇ»>
 4988    "});
 4989
 4990    cx.set_state(indoc! {"
 4991        «test
 4992         testˇ»
 4993        «test
 4994         testˇ»
 4995    "});
 4996    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4997    cx.assert_editor_state(indoc! {"
 4998        <«ˇ»>test
 4999         test</«ˇ»>
 5000        <«ˇ»>test
 5001         test</«ˇ»>
 5002    "});
 5003}
 5004
 5005#[gpui::test]
 5006async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5007    init_test(cx, |_| {});
 5008
 5009    let mut cx = EditorTestContext::new(cx).await;
 5010
 5011    let plaintext_language = Arc::new(Language::new(
 5012        LanguageConfig {
 5013            name: "Plain Text".into(),
 5014            ..LanguageConfig::default()
 5015        },
 5016        None,
 5017    ));
 5018
 5019    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5020
 5021    cx.set_state(indoc! {"
 5022        «testˇ»
 5023    "});
 5024    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5025    cx.assert_editor_state(indoc! {"
 5026      «testˇ»
 5027    "});
 5028}
 5029
 5030#[gpui::test]
 5031async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5032    init_test(cx, |_| {});
 5033
 5034    let mut cx = EditorTestContext::new(cx).await;
 5035
 5036    // Manipulate with multiple selections on a single line
 5037    cx.set_state(indoc! {"
 5038        dd«dd
 5039        cˇ»c«c
 5040        bb
 5041        aaaˇ»aa
 5042    "});
 5043    cx.update_editor(|e, window, cx| {
 5044        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5045    });
 5046    cx.assert_editor_state(indoc! {"
 5047        «aaaaa
 5048        bb
 5049        ccc
 5050        ddddˇ»
 5051    "});
 5052
 5053    // Manipulate with multiple disjoin selections
 5054    cx.set_state(indoc! {"
 5055 5056        4
 5057        3
 5058        2
 5059        1ˇ»
 5060
 5061        dd«dd
 5062        ccc
 5063        bb
 5064        aaaˇ»aa
 5065    "});
 5066    cx.update_editor(|e, window, cx| {
 5067        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5068    });
 5069    cx.assert_editor_state(indoc! {"
 5070        «1
 5071        2
 5072        3
 5073        4
 5074        5ˇ»
 5075
 5076        «aaaaa
 5077        bb
 5078        ccc
 5079        ddddˇ»
 5080    "});
 5081
 5082    // Adding lines on each selection
 5083    cx.set_state(indoc! {"
 5084 5085        1ˇ»
 5086
 5087        bb«bb
 5088        aaaˇ»aa
 5089    "});
 5090    cx.update_editor(|e, window, cx| {
 5091        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5092    });
 5093    cx.assert_editor_state(indoc! {"
 5094        «2
 5095        1
 5096        added lineˇ»
 5097
 5098        «bbbb
 5099        aaaaa
 5100        added lineˇ»
 5101    "});
 5102
 5103    // Removing lines on each selection
 5104    cx.set_state(indoc! {"
 5105 5106        1ˇ»
 5107
 5108        bb«bb
 5109        aaaˇ»aa
 5110    "});
 5111    cx.update_editor(|e, window, cx| {
 5112        e.manipulate_immutable_lines(window, cx, |lines| {
 5113            lines.pop();
 5114        })
 5115    });
 5116    cx.assert_editor_state(indoc! {"
 5117        «2ˇ»
 5118
 5119        «bbbbˇ»
 5120    "});
 5121}
 5122
 5123#[gpui::test]
 5124async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5125    init_test(cx, |settings| {
 5126        settings.defaults.tab_size = NonZeroU32::new(3)
 5127    });
 5128
 5129    let mut cx = EditorTestContext::new(cx).await;
 5130
 5131    // MULTI SELECTION
 5132    // Ln.1 "«" tests empty lines
 5133    // Ln.9 tests just leading whitespace
 5134    cx.set_state(indoc! {"
 5135        «
 5136        abc                 // No indentationˇ»
 5137        «\tabc              // 1 tabˇ»
 5138        \t\tabc «      ˇ»   // 2 tabs
 5139        \t ab«c             // Tab followed by space
 5140         \tabc              // Space followed by tab (3 spaces should be the result)
 5141        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5142           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5143        \t
 5144        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5145    "});
 5146    cx.update_editor(|e, window, cx| {
 5147        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5148    });
 5149    cx.assert_editor_state(
 5150        indoc! {"
 5151            «
 5152            abc                 // No indentation
 5153               abc              // 1 tab
 5154                  abc          // 2 tabs
 5155                abc             // Tab followed by space
 5156               abc              // Space followed by tab (3 spaces should be the result)
 5157                           abc   // Mixed indentation (tab conversion depends on the column)
 5158               abc         // Already space indented
 5159               ·
 5160               abc\tdef          // Only the leading tab is manipulatedˇ»
 5161        "}
 5162        .replace("·", "")
 5163        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5164    );
 5165
 5166    // Test on just a few lines, the others should remain unchanged
 5167    // Only lines (3, 5, 10, 11) should change
 5168    cx.set_state(
 5169        indoc! {"
 5170            ·
 5171            abc                 // No indentation
 5172            \tabcˇ               // 1 tab
 5173            \t\tabc             // 2 tabs
 5174            \t abcˇ              // Tab followed by space
 5175             \tabc              // Space followed by tab (3 spaces should be the result)
 5176            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5177               abc              // Already space indented
 5178            «\t
 5179            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5180        "}
 5181        .replace("·", "")
 5182        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5183    );
 5184    cx.update_editor(|e, window, cx| {
 5185        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5186    });
 5187    cx.assert_editor_state(
 5188        indoc! {"
 5189            ·
 5190            abc                 // No indentation
 5191            «   abc               // 1 tabˇ»
 5192            \t\tabc             // 2 tabs
 5193            «    abc              // Tab followed by spaceˇ»
 5194             \tabc              // Space followed by tab (3 spaces should be the result)
 5195            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5196               abc              // Already space indented
 5197            «   ·
 5198               abc\tdef          // Only the leading tab is manipulatedˇ»
 5199        "}
 5200        .replace("·", "")
 5201        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5202    );
 5203
 5204    // SINGLE SELECTION
 5205    // Ln.1 "«" tests empty lines
 5206    // Ln.9 tests just leading whitespace
 5207    cx.set_state(indoc! {"
 5208        «
 5209        abc                 // No indentation
 5210        \tabc               // 1 tab
 5211        \t\tabc             // 2 tabs
 5212        \t abc              // Tab followed by space
 5213         \tabc              // Space followed by tab (3 spaces should be the result)
 5214        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5215           abc              // Already space indented
 5216        \t
 5217        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5218    "});
 5219    cx.update_editor(|e, window, cx| {
 5220        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5221    });
 5222    cx.assert_editor_state(
 5223        indoc! {"
 5224            «
 5225            abc                 // No indentation
 5226               abc               // 1 tab
 5227                  abc             // 2 tabs
 5228                abc              // Tab followed by space
 5229               abc              // Space followed by tab (3 spaces should be the result)
 5230                           abc   // Mixed indentation (tab conversion depends on the column)
 5231               abc              // Already space indented
 5232               ·
 5233               abc\tdef          // Only the leading tab is manipulatedˇ»
 5234        "}
 5235        .replace("·", "")
 5236        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5237    );
 5238}
 5239
 5240#[gpui::test]
 5241async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5242    init_test(cx, |settings| {
 5243        settings.defaults.tab_size = NonZeroU32::new(3)
 5244    });
 5245
 5246    let mut cx = EditorTestContext::new(cx).await;
 5247
 5248    // MULTI SELECTION
 5249    // Ln.1 "«" tests empty lines
 5250    // Ln.11 tests just leading whitespace
 5251    cx.set_state(indoc! {"
 5252        «
 5253        abˇ»ˇc                 // No indentation
 5254         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5255          abc  «             // 2 spaces (< 3 so dont convert)
 5256           abc              // 3 spaces (convert)
 5257             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5258        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5259        «\t abc              // Tab followed by space
 5260         \tabc              // Space followed by tab (should be consumed due to tab)
 5261        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5262           \tˇ»  «\t
 5263           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5264    "});
 5265    cx.update_editor(|e, window, cx| {
 5266        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5267    });
 5268    cx.assert_editor_state(indoc! {"
 5269        «
 5270        abc                 // No indentation
 5271         abc                // 1 space (< 3 so dont convert)
 5272          abc               // 2 spaces (< 3 so dont convert)
 5273        \tabc              // 3 spaces (convert)
 5274        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5275        \t\t\tabc           // Already tab indented
 5276        \t abc              // Tab followed by space
 5277        \tabc              // Space followed by tab (should be consumed due to tab)
 5278        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5279        \t\t\t
 5280        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5281    "});
 5282
 5283    // Test on just a few lines, the other should remain unchanged
 5284    // Only lines (4, 8, 11, 12) should change
 5285    cx.set_state(
 5286        indoc! {"
 5287            ·
 5288            abc                 // No indentation
 5289             abc                // 1 space (< 3 so dont convert)
 5290              abc               // 2 spaces (< 3 so dont convert)
 5291            «   abc              // 3 spaces (convert)ˇ»
 5292                 abc            // 5 spaces (1 tab + 2 spaces)
 5293            \t\t\tabc           // Already tab indented
 5294            \t abc              // Tab followed by space
 5295             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5296               \t\t  \tabc      // Mixed indentation
 5297            \t \t  \t   \tabc   // Mixed indentation
 5298               \t  \tˇ
 5299            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5300        "}
 5301        .replace("·", "")
 5302        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5303    );
 5304    cx.update_editor(|e, window, cx| {
 5305        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5306    });
 5307    cx.assert_editor_state(
 5308        indoc! {"
 5309            ·
 5310            abc                 // No indentation
 5311             abc                // 1 space (< 3 so dont convert)
 5312              abc               // 2 spaces (< 3 so dont convert)
 5313            «\tabc              // 3 spaces (convert)ˇ»
 5314                 abc            // 5 spaces (1 tab + 2 spaces)
 5315            \t\t\tabc           // Already tab indented
 5316            \t abc              // Tab followed by space
 5317            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5318               \t\t  \tabc      // Mixed indentation
 5319            \t \t  \t   \tabc   // Mixed indentation
 5320            «\t\t\t
 5321            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5322        "}
 5323        .replace("·", "")
 5324        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5325    );
 5326
 5327    // SINGLE SELECTION
 5328    // Ln.1 "«" tests empty lines
 5329    // Ln.11 tests just leading whitespace
 5330    cx.set_state(indoc! {"
 5331        «
 5332        abc                 // No indentation
 5333         abc                // 1 space (< 3 so dont convert)
 5334          abc               // 2 spaces (< 3 so dont convert)
 5335           abc              // 3 spaces (convert)
 5336             abc            // 5 spaces (1 tab + 2 spaces)
 5337        \t\t\tabc           // Already tab indented
 5338        \t abc              // Tab followed by space
 5339         \tabc              // Space followed by tab (should be consumed due to tab)
 5340        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5341           \t  \t
 5342           abc   \t         // Only the leading spaces should be convertedˇ»
 5343    "});
 5344    cx.update_editor(|e, window, cx| {
 5345        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5346    });
 5347    cx.assert_editor_state(indoc! {"
 5348        «
 5349        abc                 // No indentation
 5350         abc                // 1 space (< 3 so dont convert)
 5351          abc               // 2 spaces (< 3 so dont convert)
 5352        \tabc              // 3 spaces (convert)
 5353        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5354        \t\t\tabc           // Already tab indented
 5355        \t abc              // Tab followed by space
 5356        \tabc              // Space followed by tab (should be consumed due to tab)
 5357        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5358        \t\t\t
 5359        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5360    "});
 5361}
 5362
 5363#[gpui::test]
 5364async fn test_toggle_case(cx: &mut TestAppContext) {
 5365    init_test(cx, |_| {});
 5366
 5367    let mut cx = EditorTestContext::new(cx).await;
 5368
 5369    // If all lower case -> upper case
 5370    cx.set_state(indoc! {"
 5371        «hello worldˇ»
 5372    "});
 5373    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5374    cx.assert_editor_state(indoc! {"
 5375        «HELLO WORLDˇ»
 5376    "});
 5377
 5378    // If all upper case -> lower case
 5379    cx.set_state(indoc! {"
 5380        «HELLO WORLDˇ»
 5381    "});
 5382    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5383    cx.assert_editor_state(indoc! {"
 5384        «hello worldˇ»
 5385    "});
 5386
 5387    // If any upper case characters are identified -> lower case
 5388    // This matches JetBrains IDEs
 5389    cx.set_state(indoc! {"
 5390        «hEllo worldˇ»
 5391    "});
 5392    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5393    cx.assert_editor_state(indoc! {"
 5394        «hello worldˇ»
 5395    "});
 5396}
 5397
 5398#[gpui::test]
 5399async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5400    init_test(cx, |_| {});
 5401
 5402    let mut cx = EditorTestContext::new(cx).await;
 5403
 5404    cx.set_state(indoc! {"
 5405        «implement-windows-supportˇ»
 5406    "});
 5407    cx.update_editor(|e, window, cx| {
 5408        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5409    });
 5410    cx.assert_editor_state(indoc! {"
 5411        «Implement windows supportˇ»
 5412    "});
 5413}
 5414
 5415#[gpui::test]
 5416async fn test_manipulate_text(cx: &mut TestAppContext) {
 5417    init_test(cx, |_| {});
 5418
 5419    let mut cx = EditorTestContext::new(cx).await;
 5420
 5421    // Test convert_to_upper_case()
 5422    cx.set_state(indoc! {"
 5423        «hello worldˇ»
 5424    "});
 5425    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5426    cx.assert_editor_state(indoc! {"
 5427        «HELLO WORLDˇ»
 5428    "});
 5429
 5430    // Test convert_to_lower_case()
 5431    cx.set_state(indoc! {"
 5432        «HELLO WORLDˇ»
 5433    "});
 5434    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5435    cx.assert_editor_state(indoc! {"
 5436        «hello worldˇ»
 5437    "});
 5438
 5439    // Test multiple line, single selection case
 5440    cx.set_state(indoc! {"
 5441        «The quick brown
 5442        fox jumps over
 5443        the lazy dogˇ»
 5444    "});
 5445    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5446    cx.assert_editor_state(indoc! {"
 5447        «The Quick Brown
 5448        Fox Jumps Over
 5449        The Lazy Dogˇ»
 5450    "});
 5451
 5452    // Test multiple line, single selection case
 5453    cx.set_state(indoc! {"
 5454        «The quick brown
 5455        fox jumps over
 5456        the lazy dogˇ»
 5457    "});
 5458    cx.update_editor(|e, window, cx| {
 5459        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5460    });
 5461    cx.assert_editor_state(indoc! {"
 5462        «TheQuickBrown
 5463        FoxJumpsOver
 5464        TheLazyDogˇ»
 5465    "});
 5466
 5467    // From here on out, test more complex cases of manipulate_text()
 5468
 5469    // Test no selection case - should affect words cursors are in
 5470    // Cursor at beginning, middle, and end of word
 5471    cx.set_state(indoc! {"
 5472        ˇhello big beauˇtiful worldˇ
 5473    "});
 5474    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5475    cx.assert_editor_state(indoc! {"
 5476        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5477    "});
 5478
 5479    // Test multiple selections on a single line and across multiple lines
 5480    cx.set_state(indoc! {"
 5481        «Theˇ» quick «brown
 5482        foxˇ» jumps «overˇ»
 5483        the «lazyˇ» dog
 5484    "});
 5485    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5486    cx.assert_editor_state(indoc! {"
 5487        «THEˇ» quick «BROWN
 5488        FOXˇ» jumps «OVERˇ»
 5489        the «LAZYˇ» dog
 5490    "});
 5491
 5492    // Test case where text length grows
 5493    cx.set_state(indoc! {"
 5494        «tschüߡ»
 5495    "});
 5496    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5497    cx.assert_editor_state(indoc! {"
 5498        «TSCHÜSSˇ»
 5499    "});
 5500
 5501    // Test to make sure we don't crash when text shrinks
 5502    cx.set_state(indoc! {"
 5503        aaa_bbbˇ
 5504    "});
 5505    cx.update_editor(|e, window, cx| {
 5506        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5507    });
 5508    cx.assert_editor_state(indoc! {"
 5509        «aaaBbbˇ»
 5510    "});
 5511
 5512    // Test to make sure we all aware of the fact that each word can grow and shrink
 5513    // Final selections should be aware of this fact
 5514    cx.set_state(indoc! {"
 5515        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5516    "});
 5517    cx.update_editor(|e, window, cx| {
 5518        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5519    });
 5520    cx.assert_editor_state(indoc! {"
 5521        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5522    "});
 5523
 5524    cx.set_state(indoc! {"
 5525        «hElLo, WoRld!ˇ»
 5526    "});
 5527    cx.update_editor(|e, window, cx| {
 5528        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5529    });
 5530    cx.assert_editor_state(indoc! {"
 5531        «HeLlO, wOrLD!ˇ»
 5532    "});
 5533
 5534    // Test selections with `line_mode() = true`.
 5535    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5536    cx.set_state(indoc! {"
 5537        «The quick brown
 5538        fox jumps over
 5539        tˇ»he lazy dog
 5540    "});
 5541    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5542    cx.assert_editor_state(indoc! {"
 5543        «THE QUICK BROWN
 5544        FOX JUMPS OVER
 5545        THE LAZY DOGˇ»
 5546    "});
 5547}
 5548
 5549#[gpui::test]
 5550fn test_duplicate_line(cx: &mut TestAppContext) {
 5551    init_test(cx, |_| {});
 5552
 5553    let editor = cx.add_window(|window, cx| {
 5554        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5555        build_editor(buffer, window, cx)
 5556    });
 5557    _ = editor.update(cx, |editor, window, cx| {
 5558        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5559            s.select_display_ranges([
 5560                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5561                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5562                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5563                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5564            ])
 5565        });
 5566        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5567        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5568        assert_eq!(
 5569            editor.selections.display_ranges(cx),
 5570            vec![
 5571                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5572                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5573                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5574                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5575            ]
 5576        );
 5577    });
 5578
 5579    let editor = cx.add_window(|window, cx| {
 5580        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5581        build_editor(buffer, window, cx)
 5582    });
 5583    _ = editor.update(cx, |editor, window, cx| {
 5584        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5585            s.select_display_ranges([
 5586                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5587                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5588            ])
 5589        });
 5590        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5591        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5592        assert_eq!(
 5593            editor.selections.display_ranges(cx),
 5594            vec![
 5595                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5596                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5597            ]
 5598        );
 5599    });
 5600
 5601    // With `move_upwards` the selections stay in place, except for
 5602    // the lines inserted above them
 5603    let editor = cx.add_window(|window, cx| {
 5604        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5605        build_editor(buffer, window, cx)
 5606    });
 5607    _ = editor.update(cx, |editor, window, cx| {
 5608        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5609            s.select_display_ranges([
 5610                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5611                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5612                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5613                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5614            ])
 5615        });
 5616        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5617        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5618        assert_eq!(
 5619            editor.selections.display_ranges(cx),
 5620            vec![
 5621                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5622                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5623                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5624                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5625            ]
 5626        );
 5627    });
 5628
 5629    let editor = cx.add_window(|window, cx| {
 5630        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5631        build_editor(buffer, window, cx)
 5632    });
 5633    _ = editor.update(cx, |editor, window, cx| {
 5634        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5635            s.select_display_ranges([
 5636                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5637                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5638            ])
 5639        });
 5640        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5641        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5642        assert_eq!(
 5643            editor.selections.display_ranges(cx),
 5644            vec![
 5645                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5646                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5647            ]
 5648        );
 5649    });
 5650
 5651    let editor = cx.add_window(|window, cx| {
 5652        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5653        build_editor(buffer, window, cx)
 5654    });
 5655    _ = editor.update(cx, |editor, window, cx| {
 5656        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5657            s.select_display_ranges([
 5658                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5659                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5660            ])
 5661        });
 5662        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5663        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5664        assert_eq!(
 5665            editor.selections.display_ranges(cx),
 5666            vec![
 5667                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5668                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5669            ]
 5670        );
 5671    });
 5672}
 5673
 5674#[gpui::test]
 5675fn test_move_line_up_down(cx: &mut TestAppContext) {
 5676    init_test(cx, |_| {});
 5677
 5678    let editor = cx.add_window(|window, cx| {
 5679        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5680        build_editor(buffer, window, cx)
 5681    });
 5682    _ = editor.update(cx, |editor, window, cx| {
 5683        editor.fold_creases(
 5684            vec![
 5685                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5686                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5687                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5688            ],
 5689            true,
 5690            window,
 5691            cx,
 5692        );
 5693        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5694            s.select_display_ranges([
 5695                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5696                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5697                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5698                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5699            ])
 5700        });
 5701        assert_eq!(
 5702            editor.display_text(cx),
 5703            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5704        );
 5705
 5706        editor.move_line_up(&MoveLineUp, window, cx);
 5707        assert_eq!(
 5708            editor.display_text(cx),
 5709            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5710        );
 5711        assert_eq!(
 5712            editor.selections.display_ranges(cx),
 5713            vec![
 5714                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5715                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5716                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5717                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5718            ]
 5719        );
 5720    });
 5721
 5722    _ = editor.update(cx, |editor, window, cx| {
 5723        editor.move_line_down(&MoveLineDown, window, cx);
 5724        assert_eq!(
 5725            editor.display_text(cx),
 5726            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5727        );
 5728        assert_eq!(
 5729            editor.selections.display_ranges(cx),
 5730            vec![
 5731                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5732                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5733                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5734                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5735            ]
 5736        );
 5737    });
 5738
 5739    _ = editor.update(cx, |editor, window, cx| {
 5740        editor.move_line_down(&MoveLineDown, window, cx);
 5741        assert_eq!(
 5742            editor.display_text(cx),
 5743            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5744        );
 5745        assert_eq!(
 5746            editor.selections.display_ranges(cx),
 5747            vec![
 5748                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5749                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5750                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5751                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5752            ]
 5753        );
 5754    });
 5755
 5756    _ = editor.update(cx, |editor, window, cx| {
 5757        editor.move_line_up(&MoveLineUp, window, cx);
 5758        assert_eq!(
 5759            editor.display_text(cx),
 5760            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5761        );
 5762        assert_eq!(
 5763            editor.selections.display_ranges(cx),
 5764            vec![
 5765                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5766                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5767                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5768                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5769            ]
 5770        );
 5771    });
 5772}
 5773
 5774#[gpui::test]
 5775fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5776    init_test(cx, |_| {});
 5777    let editor = cx.add_window(|window, cx| {
 5778        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5779        build_editor(buffer, window, cx)
 5780    });
 5781    _ = editor.update(cx, |editor, window, cx| {
 5782        editor.fold_creases(
 5783            vec![Crease::simple(
 5784                Point::new(6, 4)..Point::new(7, 4),
 5785                FoldPlaceholder::test(),
 5786            )],
 5787            true,
 5788            window,
 5789            cx,
 5790        );
 5791        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5792            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5793        });
 5794        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5795        editor.move_line_up(&MoveLineUp, window, cx);
 5796        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5797        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5798    });
 5799}
 5800
 5801#[gpui::test]
 5802fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5803    init_test(cx, |_| {});
 5804
 5805    let editor = cx.add_window(|window, cx| {
 5806        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5807        build_editor(buffer, window, cx)
 5808    });
 5809    _ = editor.update(cx, |editor, window, cx| {
 5810        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5811        editor.insert_blocks(
 5812            [BlockProperties {
 5813                style: BlockStyle::Fixed,
 5814                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5815                height: Some(1),
 5816                render: Arc::new(|_| div().into_any()),
 5817                priority: 0,
 5818            }],
 5819            Some(Autoscroll::fit()),
 5820            cx,
 5821        );
 5822        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5823            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5824        });
 5825        editor.move_line_down(&MoveLineDown, window, cx);
 5826    });
 5827}
 5828
 5829#[gpui::test]
 5830async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5831    init_test(cx, |_| {});
 5832
 5833    let mut cx = EditorTestContext::new(cx).await;
 5834    cx.set_state(
 5835        &"
 5836            ˇzero
 5837            one
 5838            two
 5839            three
 5840            four
 5841            five
 5842        "
 5843        .unindent(),
 5844    );
 5845
 5846    // Create a four-line block that replaces three lines of text.
 5847    cx.update_editor(|editor, window, cx| {
 5848        let snapshot = editor.snapshot(window, cx);
 5849        let snapshot = &snapshot.buffer_snapshot();
 5850        let placement = BlockPlacement::Replace(
 5851            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5852        );
 5853        editor.insert_blocks(
 5854            [BlockProperties {
 5855                placement,
 5856                height: Some(4),
 5857                style: BlockStyle::Sticky,
 5858                render: Arc::new(|_| gpui::div().into_any_element()),
 5859                priority: 0,
 5860            }],
 5861            None,
 5862            cx,
 5863        );
 5864    });
 5865
 5866    // Move down so that the cursor touches the block.
 5867    cx.update_editor(|editor, window, cx| {
 5868        editor.move_down(&Default::default(), window, cx);
 5869    });
 5870    cx.assert_editor_state(
 5871        &"
 5872            zero
 5873            «one
 5874            two
 5875            threeˇ»
 5876            four
 5877            five
 5878        "
 5879        .unindent(),
 5880    );
 5881
 5882    // Move down past the block.
 5883    cx.update_editor(|editor, window, cx| {
 5884        editor.move_down(&Default::default(), window, cx);
 5885    });
 5886    cx.assert_editor_state(
 5887        &"
 5888            zero
 5889            one
 5890            two
 5891            three
 5892            ˇfour
 5893            five
 5894        "
 5895        .unindent(),
 5896    );
 5897}
 5898
 5899#[gpui::test]
 5900fn test_transpose(cx: &mut TestAppContext) {
 5901    init_test(cx, |_| {});
 5902
 5903    _ = cx.add_window(|window, cx| {
 5904        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5905        editor.set_style(EditorStyle::default(), window, cx);
 5906        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5907            s.select_ranges([1..1])
 5908        });
 5909        editor.transpose(&Default::default(), window, cx);
 5910        assert_eq!(editor.text(cx), "bac");
 5911        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5912
 5913        editor.transpose(&Default::default(), window, cx);
 5914        assert_eq!(editor.text(cx), "bca");
 5915        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5916
 5917        editor.transpose(&Default::default(), window, cx);
 5918        assert_eq!(editor.text(cx), "bac");
 5919        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5920
 5921        editor
 5922    });
 5923
 5924    _ = cx.add_window(|window, cx| {
 5925        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5926        editor.set_style(EditorStyle::default(), window, cx);
 5927        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5928            s.select_ranges([3..3])
 5929        });
 5930        editor.transpose(&Default::default(), window, cx);
 5931        assert_eq!(editor.text(cx), "acb\nde");
 5932        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5933
 5934        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5935            s.select_ranges([4..4])
 5936        });
 5937        editor.transpose(&Default::default(), window, cx);
 5938        assert_eq!(editor.text(cx), "acbd\ne");
 5939        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5940
 5941        editor.transpose(&Default::default(), window, cx);
 5942        assert_eq!(editor.text(cx), "acbde\n");
 5943        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5944
 5945        editor.transpose(&Default::default(), window, cx);
 5946        assert_eq!(editor.text(cx), "acbd\ne");
 5947        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5948
 5949        editor
 5950    });
 5951
 5952    _ = cx.add_window(|window, cx| {
 5953        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5954        editor.set_style(EditorStyle::default(), window, cx);
 5955        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5956            s.select_ranges([1..1, 2..2, 4..4])
 5957        });
 5958        editor.transpose(&Default::default(), window, cx);
 5959        assert_eq!(editor.text(cx), "bacd\ne");
 5960        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5961
 5962        editor.transpose(&Default::default(), window, cx);
 5963        assert_eq!(editor.text(cx), "bcade\n");
 5964        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5965
 5966        editor.transpose(&Default::default(), window, cx);
 5967        assert_eq!(editor.text(cx), "bcda\ne");
 5968        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5969
 5970        editor.transpose(&Default::default(), window, cx);
 5971        assert_eq!(editor.text(cx), "bcade\n");
 5972        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5973
 5974        editor.transpose(&Default::default(), window, cx);
 5975        assert_eq!(editor.text(cx), "bcaed\n");
 5976        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5977
 5978        editor
 5979    });
 5980
 5981    _ = cx.add_window(|window, cx| {
 5982        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5983        editor.set_style(EditorStyle::default(), window, cx);
 5984        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5985            s.select_ranges([4..4])
 5986        });
 5987        editor.transpose(&Default::default(), window, cx);
 5988        assert_eq!(editor.text(cx), "🏀🍐✋");
 5989        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5990
 5991        editor.transpose(&Default::default(), window, cx);
 5992        assert_eq!(editor.text(cx), "🏀✋🍐");
 5993        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5994
 5995        editor.transpose(&Default::default(), window, cx);
 5996        assert_eq!(editor.text(cx), "🏀🍐✋");
 5997        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5998
 5999        editor
 6000    });
 6001}
 6002
 6003#[gpui::test]
 6004async fn test_rewrap(cx: &mut TestAppContext) {
 6005    init_test(cx, |settings| {
 6006        settings.languages.0.extend([
 6007            (
 6008                "Markdown".into(),
 6009                LanguageSettingsContent {
 6010                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6011                    preferred_line_length: Some(40),
 6012                    ..Default::default()
 6013                },
 6014            ),
 6015            (
 6016                "Plain Text".into(),
 6017                LanguageSettingsContent {
 6018                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6019                    preferred_line_length: Some(40),
 6020                    ..Default::default()
 6021                },
 6022            ),
 6023            (
 6024                "C++".into(),
 6025                LanguageSettingsContent {
 6026                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6027                    preferred_line_length: Some(40),
 6028                    ..Default::default()
 6029                },
 6030            ),
 6031            (
 6032                "Python".into(),
 6033                LanguageSettingsContent {
 6034                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6035                    preferred_line_length: Some(40),
 6036                    ..Default::default()
 6037                },
 6038            ),
 6039            (
 6040                "Rust".into(),
 6041                LanguageSettingsContent {
 6042                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6043                    preferred_line_length: Some(40),
 6044                    ..Default::default()
 6045                },
 6046            ),
 6047        ])
 6048    });
 6049
 6050    let mut cx = EditorTestContext::new(cx).await;
 6051
 6052    let cpp_language = Arc::new(Language::new(
 6053        LanguageConfig {
 6054            name: "C++".into(),
 6055            line_comments: vec!["// ".into()],
 6056            ..LanguageConfig::default()
 6057        },
 6058        None,
 6059    ));
 6060    let python_language = Arc::new(Language::new(
 6061        LanguageConfig {
 6062            name: "Python".into(),
 6063            line_comments: vec!["# ".into()],
 6064            ..LanguageConfig::default()
 6065        },
 6066        None,
 6067    ));
 6068    let markdown_language = Arc::new(Language::new(
 6069        LanguageConfig {
 6070            name: "Markdown".into(),
 6071            rewrap_prefixes: vec![
 6072                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6073                regex::Regex::new("[-*+]\\s+").unwrap(),
 6074            ],
 6075            ..LanguageConfig::default()
 6076        },
 6077        None,
 6078    ));
 6079    let rust_language = Arc::new(
 6080        Language::new(
 6081            LanguageConfig {
 6082                name: "Rust".into(),
 6083                line_comments: vec!["// ".into(), "/// ".into()],
 6084                ..LanguageConfig::default()
 6085            },
 6086            Some(tree_sitter_rust::LANGUAGE.into()),
 6087        )
 6088        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6089        .unwrap(),
 6090    );
 6091
 6092    let plaintext_language = Arc::new(Language::new(
 6093        LanguageConfig {
 6094            name: "Plain Text".into(),
 6095            ..LanguageConfig::default()
 6096        },
 6097        None,
 6098    ));
 6099
 6100    // Test basic rewrapping of a long line with a cursor
 6101    assert_rewrap(
 6102        indoc! {"
 6103            // ˇThis is a long comment that needs to be wrapped.
 6104        "},
 6105        indoc! {"
 6106            // ˇThis is a long comment that needs to
 6107            // be wrapped.
 6108        "},
 6109        cpp_language.clone(),
 6110        &mut cx,
 6111    );
 6112
 6113    // Test rewrapping a full selection
 6114    assert_rewrap(
 6115        indoc! {"
 6116            «// This selected long comment needs to be wrapped.ˇ»"
 6117        },
 6118        indoc! {"
 6119            «// This selected long comment needs to
 6120            // be wrapped.ˇ»"
 6121        },
 6122        cpp_language.clone(),
 6123        &mut cx,
 6124    );
 6125
 6126    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6127    assert_rewrap(
 6128        indoc! {"
 6129            // ˇThis is the first line.
 6130            // Thisˇ is the second line.
 6131            // This is the thirdˇ line, all part of one paragraph.
 6132         "},
 6133        indoc! {"
 6134            // ˇThis is the first line. Thisˇ is the
 6135            // second line. This is the thirdˇ line,
 6136            // all part of one paragraph.
 6137         "},
 6138        cpp_language.clone(),
 6139        &mut cx,
 6140    );
 6141
 6142    // Test multiple cursors in different paragraphs trigger separate rewraps
 6143    assert_rewrap(
 6144        indoc! {"
 6145            // ˇThis is the first paragraph, first line.
 6146            // ˇThis is the first paragraph, second line.
 6147
 6148            // ˇThis is the second paragraph, first line.
 6149            // ˇThis is the second paragraph, second line.
 6150        "},
 6151        indoc! {"
 6152            // ˇThis is the first paragraph, first
 6153            // line. ˇThis is the first paragraph,
 6154            // second line.
 6155
 6156            // ˇThis is the second paragraph, first
 6157            // line. ˇThis is the second paragraph,
 6158            // second line.
 6159        "},
 6160        cpp_language.clone(),
 6161        &mut cx,
 6162    );
 6163
 6164    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6165    assert_rewrap(
 6166        indoc! {"
 6167            «// A regular long long comment to be wrapped.
 6168            /// A documentation long comment to be wrapped.ˇ»
 6169          "},
 6170        indoc! {"
 6171            «// A regular long long comment to be
 6172            // wrapped.
 6173            /// A documentation long comment to be
 6174            /// wrapped.ˇ»
 6175          "},
 6176        rust_language.clone(),
 6177        &mut cx,
 6178    );
 6179
 6180    // Test that change in indentation level trigger seperate rewraps
 6181    assert_rewrap(
 6182        indoc! {"
 6183            fn foo() {
 6184                «// This is a long comment at the base indent.
 6185                    // This is a long comment at the next indent.ˇ»
 6186            }
 6187        "},
 6188        indoc! {"
 6189            fn foo() {
 6190                «// This is a long comment at the
 6191                // base indent.
 6192                    // This is a long comment at the
 6193                    // next indent.ˇ»
 6194            }
 6195        "},
 6196        rust_language.clone(),
 6197        &mut cx,
 6198    );
 6199
 6200    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6201    assert_rewrap(
 6202        indoc! {"
 6203            # ˇThis is a long comment using a pound sign.
 6204        "},
 6205        indoc! {"
 6206            # ˇThis is a long comment using a pound
 6207            # sign.
 6208        "},
 6209        python_language,
 6210        &mut cx,
 6211    );
 6212
 6213    // Test rewrapping only affects comments, not code even when selected
 6214    assert_rewrap(
 6215        indoc! {"
 6216            «/// This doc comment is long and should be wrapped.
 6217            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6218        "},
 6219        indoc! {"
 6220            «/// This doc comment is long and should
 6221            /// be wrapped.
 6222            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6223        "},
 6224        rust_language.clone(),
 6225        &mut cx,
 6226    );
 6227
 6228    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6229    assert_rewrap(
 6230        indoc! {"
 6231            # Header
 6232
 6233            A long long long line of markdown text to wrap.ˇ
 6234         "},
 6235        indoc! {"
 6236            # Header
 6237
 6238            A long long long line of markdown text
 6239            to wrap.ˇ
 6240         "},
 6241        markdown_language.clone(),
 6242        &mut cx,
 6243    );
 6244
 6245    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6246    assert_rewrap(
 6247        indoc! {"
 6248            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6249            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6250            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6251        "},
 6252        indoc! {"
 6253            «1. This is a numbered list item that is
 6254               very long and needs to be wrapped
 6255               properly.
 6256            2. This is a numbered list item that is
 6257               very long and needs to be wrapped
 6258               properly.
 6259            - This is an unordered list item that is
 6260              also very long and should not merge
 6261              with the numbered item.ˇ»
 6262        "},
 6263        markdown_language.clone(),
 6264        &mut cx,
 6265    );
 6266
 6267    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6268    assert_rewrap(
 6269        indoc! {"
 6270            «1. This is a numbered list item that is
 6271            very long and needs to be wrapped
 6272            properly.
 6273            2. This is a numbered list item that is
 6274            very long and needs to be wrapped
 6275            properly.
 6276            - This is an unordered list item that is
 6277            also very long and should not merge with
 6278            the numbered item.ˇ»
 6279        "},
 6280        indoc! {"
 6281            «1. This is a numbered list item that is
 6282               very long and needs to be wrapped
 6283               properly.
 6284            2. This is a numbered list item that is
 6285               very long and needs to be wrapped
 6286               properly.
 6287            - This is an unordered list item that is
 6288              also very long and should not merge
 6289              with the numbered item.ˇ»
 6290        "},
 6291        markdown_language.clone(),
 6292        &mut cx,
 6293    );
 6294
 6295    // Test that rewrapping maintain indents even when they already exists.
 6296    assert_rewrap(
 6297        indoc! {"
 6298            «1. This is a numbered list
 6299               item that is very long and needs to be wrapped properly.
 6300            2. This is a numbered list
 6301               item that is very long and needs to be wrapped properly.
 6302            - This is an unordered list item that is also very long and
 6303              should not merge with the numbered item.ˇ»
 6304        "},
 6305        indoc! {"
 6306            «1. This is a numbered list item that is
 6307               very long and needs to be wrapped
 6308               properly.
 6309            2. This is a numbered list item that is
 6310               very long and needs to be wrapped
 6311               properly.
 6312            - This is an unordered list item that is
 6313              also very long and should not merge
 6314              with the numbered item.ˇ»
 6315        "},
 6316        markdown_language,
 6317        &mut cx,
 6318    );
 6319
 6320    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6321    assert_rewrap(
 6322        indoc! {"
 6323            ˇThis is a very long line of plain text that will be wrapped.
 6324        "},
 6325        indoc! {"
 6326            ˇThis is a very long line of plain text
 6327            that will be wrapped.
 6328        "},
 6329        plaintext_language.clone(),
 6330        &mut cx,
 6331    );
 6332
 6333    // Test that non-commented code acts as a paragraph boundary within a selection
 6334    assert_rewrap(
 6335        indoc! {"
 6336               «// This is the first long comment block to be wrapped.
 6337               fn my_func(a: u32);
 6338               // This is the second long comment block to be wrapped.ˇ»
 6339           "},
 6340        indoc! {"
 6341               «// This is the first long comment block
 6342               // to be wrapped.
 6343               fn my_func(a: u32);
 6344               // This is the second long comment block
 6345               // to be wrapped.ˇ»
 6346           "},
 6347        rust_language,
 6348        &mut cx,
 6349    );
 6350
 6351    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6352    assert_rewrap(
 6353        indoc! {"
 6354            «ˇThis is a very long line that will be wrapped.
 6355
 6356            This is another paragraph in the same selection.»
 6357
 6358            «\tThis is a very long indented line that will be wrapped.ˇ»
 6359         "},
 6360        indoc! {"
 6361            «ˇThis is a very long line that will be
 6362            wrapped.
 6363
 6364            This is another paragraph in the same
 6365            selection.»
 6366
 6367            «\tThis is a very long indented line
 6368            \tthat will be wrapped.ˇ»
 6369         "},
 6370        plaintext_language,
 6371        &mut cx,
 6372    );
 6373
 6374    // Test that an empty comment line acts as a paragraph boundary
 6375    assert_rewrap(
 6376        indoc! {"
 6377            // ˇThis is a long comment that will be wrapped.
 6378            //
 6379            // And this is another long comment that will also be wrapped.ˇ
 6380         "},
 6381        indoc! {"
 6382            // ˇThis is a long comment that will be
 6383            // wrapped.
 6384            //
 6385            // And this is another long comment that
 6386            // will also be wrapped.ˇ
 6387         "},
 6388        cpp_language,
 6389        &mut cx,
 6390    );
 6391
 6392    #[track_caller]
 6393    fn assert_rewrap(
 6394        unwrapped_text: &str,
 6395        wrapped_text: &str,
 6396        language: Arc<Language>,
 6397        cx: &mut EditorTestContext,
 6398    ) {
 6399        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6400        cx.set_state(unwrapped_text);
 6401        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6402        cx.assert_editor_state(wrapped_text);
 6403    }
 6404}
 6405
 6406#[gpui::test]
 6407async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6408    init_test(cx, |settings| {
 6409        settings.languages.0.extend([(
 6410            "Rust".into(),
 6411            LanguageSettingsContent {
 6412                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6413                preferred_line_length: Some(40),
 6414                ..Default::default()
 6415            },
 6416        )])
 6417    });
 6418
 6419    let mut cx = EditorTestContext::new(cx).await;
 6420
 6421    let rust_lang = Arc::new(
 6422        Language::new(
 6423            LanguageConfig {
 6424                name: "Rust".into(),
 6425                line_comments: vec!["// ".into()],
 6426                block_comment: Some(BlockCommentConfig {
 6427                    start: "/*".into(),
 6428                    end: "*/".into(),
 6429                    prefix: "* ".into(),
 6430                    tab_size: 1,
 6431                }),
 6432                documentation_comment: Some(BlockCommentConfig {
 6433                    start: "/**".into(),
 6434                    end: "*/".into(),
 6435                    prefix: "* ".into(),
 6436                    tab_size: 1,
 6437                }),
 6438
 6439                ..LanguageConfig::default()
 6440            },
 6441            Some(tree_sitter_rust::LANGUAGE.into()),
 6442        )
 6443        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6444        .unwrap(),
 6445    );
 6446
 6447    // regular block comment
 6448    assert_rewrap(
 6449        indoc! {"
 6450            /*
 6451             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6452             */
 6453            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6454        "},
 6455        indoc! {"
 6456            /*
 6457             *ˇ Lorem ipsum dolor sit amet,
 6458             * consectetur adipiscing elit.
 6459             */
 6460            /*
 6461             *ˇ Lorem ipsum dolor sit amet,
 6462             * consectetur adipiscing elit.
 6463             */
 6464        "},
 6465        rust_lang.clone(),
 6466        &mut cx,
 6467    );
 6468
 6469    // indent is respected
 6470    assert_rewrap(
 6471        indoc! {"
 6472            {}
 6473                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6474        "},
 6475        indoc! {"
 6476            {}
 6477                /*
 6478                 *ˇ Lorem ipsum dolor sit amet,
 6479                 * consectetur adipiscing elit.
 6480                 */
 6481        "},
 6482        rust_lang.clone(),
 6483        &mut cx,
 6484    );
 6485
 6486    // short block comments with inline delimiters
 6487    assert_rewrap(
 6488        indoc! {"
 6489            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6490            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6491             */
 6492            /*
 6493             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6494        "},
 6495        indoc! {"
 6496            /*
 6497             *ˇ Lorem ipsum dolor sit amet,
 6498             * consectetur adipiscing elit.
 6499             */
 6500            /*
 6501             *ˇ Lorem ipsum dolor sit amet,
 6502             * consectetur adipiscing elit.
 6503             */
 6504            /*
 6505             *ˇ Lorem ipsum dolor sit amet,
 6506             * consectetur adipiscing elit.
 6507             */
 6508        "},
 6509        rust_lang.clone(),
 6510        &mut cx,
 6511    );
 6512
 6513    // multiline block comment with inline start/end delimiters
 6514    assert_rewrap(
 6515        indoc! {"
 6516            /*ˇ Lorem ipsum dolor sit amet,
 6517             * consectetur adipiscing elit. */
 6518        "},
 6519        indoc! {"
 6520            /*
 6521             *ˇ Lorem ipsum dolor sit amet,
 6522             * consectetur adipiscing elit.
 6523             */
 6524        "},
 6525        rust_lang.clone(),
 6526        &mut cx,
 6527    );
 6528
 6529    // block comment rewrap still respects paragraph bounds
 6530    assert_rewrap(
 6531        indoc! {"
 6532            /*
 6533             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6534             *
 6535             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6536             */
 6537        "},
 6538        indoc! {"
 6539            /*
 6540             *ˇ Lorem ipsum dolor sit amet,
 6541             * consectetur adipiscing elit.
 6542             *
 6543             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6544             */
 6545        "},
 6546        rust_lang.clone(),
 6547        &mut cx,
 6548    );
 6549
 6550    // documentation comments
 6551    assert_rewrap(
 6552        indoc! {"
 6553            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6554            /**
 6555             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6556             */
 6557        "},
 6558        indoc! {"
 6559            /**
 6560             *ˇ Lorem ipsum dolor sit amet,
 6561             * consectetur adipiscing elit.
 6562             */
 6563            /**
 6564             *ˇ Lorem ipsum dolor sit amet,
 6565             * consectetur adipiscing elit.
 6566             */
 6567        "},
 6568        rust_lang.clone(),
 6569        &mut cx,
 6570    );
 6571
 6572    // different, adjacent comments
 6573    assert_rewrap(
 6574        indoc! {"
 6575            /**
 6576             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6577             */
 6578            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6579            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6580        "},
 6581        indoc! {"
 6582            /**
 6583             *ˇ Lorem ipsum dolor sit amet,
 6584             * consectetur adipiscing elit.
 6585             */
 6586            /*
 6587             *ˇ Lorem ipsum dolor sit amet,
 6588             * consectetur adipiscing elit.
 6589             */
 6590            //ˇ Lorem ipsum dolor sit amet,
 6591            // consectetur adipiscing elit.
 6592        "},
 6593        rust_lang.clone(),
 6594        &mut cx,
 6595    );
 6596
 6597    // selection w/ single short block comment
 6598    assert_rewrap(
 6599        indoc! {"
 6600            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6601        "},
 6602        indoc! {"
 6603            «/*
 6604             * Lorem ipsum dolor sit amet,
 6605             * consectetur adipiscing elit.
 6606             */ˇ»
 6607        "},
 6608        rust_lang.clone(),
 6609        &mut cx,
 6610    );
 6611
 6612    // rewrapping a single comment w/ abutting comments
 6613    assert_rewrap(
 6614        indoc! {"
 6615            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6616            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6617        "},
 6618        indoc! {"
 6619            /*
 6620             * ˇLorem ipsum dolor sit amet,
 6621             * consectetur adipiscing elit.
 6622             */
 6623            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6624        "},
 6625        rust_lang.clone(),
 6626        &mut cx,
 6627    );
 6628
 6629    // selection w/ non-abutting short block comments
 6630    assert_rewrap(
 6631        indoc! {"
 6632            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6633
 6634            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6635        "},
 6636        indoc! {"
 6637            «/*
 6638             * Lorem ipsum dolor sit amet,
 6639             * consectetur adipiscing elit.
 6640             */
 6641
 6642            /*
 6643             * Lorem ipsum dolor sit amet,
 6644             * consectetur adipiscing elit.
 6645             */ˇ»
 6646        "},
 6647        rust_lang.clone(),
 6648        &mut cx,
 6649    );
 6650
 6651    // selection of multiline block comments
 6652    assert_rewrap(
 6653        indoc! {"
 6654            «/* Lorem ipsum dolor sit amet,
 6655             * consectetur adipiscing elit. */ˇ»
 6656        "},
 6657        indoc! {"
 6658            «/*
 6659             * Lorem ipsum dolor sit amet,
 6660             * consectetur adipiscing elit.
 6661             */ˇ»
 6662        "},
 6663        rust_lang.clone(),
 6664        &mut cx,
 6665    );
 6666
 6667    // partial selection of multiline block comments
 6668    assert_rewrap(
 6669        indoc! {"
 6670            «/* Lorem ipsum dolor sit amet,ˇ»
 6671             * consectetur adipiscing elit. */
 6672            /* Lorem ipsum dolor sit amet,
 6673             «* consectetur adipiscing elit. */ˇ»
 6674        "},
 6675        indoc! {"
 6676            «/*
 6677             * Lorem ipsum dolor sit amet,ˇ»
 6678             * consectetur adipiscing elit. */
 6679            /* Lorem ipsum dolor sit amet,
 6680             «* consectetur adipiscing elit.
 6681             */ˇ»
 6682        "},
 6683        rust_lang.clone(),
 6684        &mut cx,
 6685    );
 6686
 6687    // selection w/ abutting short block comments
 6688    // TODO: should not be combined; should rewrap as 2 comments
 6689    assert_rewrap(
 6690        indoc! {"
 6691            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6692            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6693        "},
 6694        // desired behavior:
 6695        // indoc! {"
 6696        //     «/*
 6697        //      * Lorem ipsum dolor sit amet,
 6698        //      * consectetur adipiscing elit.
 6699        //      */
 6700        //     /*
 6701        //      * Lorem ipsum dolor sit amet,
 6702        //      * consectetur adipiscing elit.
 6703        //      */ˇ»
 6704        // "},
 6705        // actual behaviour:
 6706        indoc! {"
 6707            «/*
 6708             * Lorem ipsum dolor sit amet,
 6709             * consectetur adipiscing elit. Lorem
 6710             * ipsum dolor sit amet, consectetur
 6711             * adipiscing elit.
 6712             */ˇ»
 6713        "},
 6714        rust_lang.clone(),
 6715        &mut cx,
 6716    );
 6717
 6718    // TODO: same as above, but with delimiters on separate line
 6719    // assert_rewrap(
 6720    //     indoc! {"
 6721    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6722    //          */
 6723    //         /*
 6724    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6725    //     "},
 6726    //     // desired:
 6727    //     // indoc! {"
 6728    //     //     «/*
 6729    //     //      * Lorem ipsum dolor sit amet,
 6730    //     //      * consectetur adipiscing elit.
 6731    //     //      */
 6732    //     //     /*
 6733    //     //      * Lorem ipsum dolor sit amet,
 6734    //     //      * consectetur adipiscing elit.
 6735    //     //      */ˇ»
 6736    //     // "},
 6737    //     // actual: (but with trailing w/s on the empty lines)
 6738    //     indoc! {"
 6739    //         «/*
 6740    //          * Lorem ipsum dolor sit amet,
 6741    //          * consectetur adipiscing elit.
 6742    //          *
 6743    //          */
 6744    //         /*
 6745    //          *
 6746    //          * Lorem ipsum dolor sit amet,
 6747    //          * consectetur adipiscing elit.
 6748    //          */ˇ»
 6749    //     "},
 6750    //     rust_lang.clone(),
 6751    //     &mut cx,
 6752    // );
 6753
 6754    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6755    assert_rewrap(
 6756        indoc! {"
 6757            /*
 6758             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6759             */
 6760            /*
 6761             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6762            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6763        "},
 6764        // desired:
 6765        // indoc! {"
 6766        //     /*
 6767        //      *ˇ Lorem ipsum dolor sit amet,
 6768        //      * consectetur adipiscing elit.
 6769        //      */
 6770        //     /*
 6771        //      *ˇ Lorem ipsum dolor sit amet,
 6772        //      * consectetur adipiscing elit.
 6773        //      */
 6774        //     /*
 6775        //      *ˇ Lorem ipsum dolor sit amet
 6776        //      */ /* consectetur adipiscing elit. */
 6777        // "},
 6778        // actual:
 6779        indoc! {"
 6780            /*
 6781             //ˇ Lorem ipsum dolor sit amet,
 6782             // consectetur adipiscing elit.
 6783             */
 6784            /*
 6785             * //ˇ Lorem ipsum dolor sit amet,
 6786             * consectetur adipiscing elit.
 6787             */
 6788            /*
 6789             *ˇ Lorem ipsum dolor sit amet */ /*
 6790             * consectetur adipiscing elit.
 6791             */
 6792        "},
 6793        rust_lang,
 6794        &mut cx,
 6795    );
 6796
 6797    #[track_caller]
 6798    fn assert_rewrap(
 6799        unwrapped_text: &str,
 6800        wrapped_text: &str,
 6801        language: Arc<Language>,
 6802        cx: &mut EditorTestContext,
 6803    ) {
 6804        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6805        cx.set_state(unwrapped_text);
 6806        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6807        cx.assert_editor_state(wrapped_text);
 6808    }
 6809}
 6810
 6811#[gpui::test]
 6812async fn test_hard_wrap(cx: &mut TestAppContext) {
 6813    init_test(cx, |_| {});
 6814    let mut cx = EditorTestContext::new(cx).await;
 6815
 6816    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6817    cx.update_editor(|editor, _, cx| {
 6818        editor.set_hard_wrap(Some(14), cx);
 6819    });
 6820
 6821    cx.set_state(indoc!(
 6822        "
 6823        one two three ˇ
 6824        "
 6825    ));
 6826    cx.simulate_input("four");
 6827    cx.run_until_parked();
 6828
 6829    cx.assert_editor_state(indoc!(
 6830        "
 6831        one two three
 6832        fourˇ
 6833        "
 6834    ));
 6835
 6836    cx.update_editor(|editor, window, cx| {
 6837        editor.newline(&Default::default(), window, cx);
 6838    });
 6839    cx.run_until_parked();
 6840    cx.assert_editor_state(indoc!(
 6841        "
 6842        one two three
 6843        four
 6844        ˇ
 6845        "
 6846    ));
 6847
 6848    cx.simulate_input("five");
 6849    cx.run_until_parked();
 6850    cx.assert_editor_state(indoc!(
 6851        "
 6852        one two three
 6853        four
 6854        fiveˇ
 6855        "
 6856    ));
 6857
 6858    cx.update_editor(|editor, window, cx| {
 6859        editor.newline(&Default::default(), window, cx);
 6860    });
 6861    cx.run_until_parked();
 6862    cx.simulate_input("# ");
 6863    cx.run_until_parked();
 6864    cx.assert_editor_state(indoc!(
 6865        "
 6866        one two three
 6867        four
 6868        five
 6869        # ˇ
 6870        "
 6871    ));
 6872
 6873    cx.update_editor(|editor, window, cx| {
 6874        editor.newline(&Default::default(), window, cx);
 6875    });
 6876    cx.run_until_parked();
 6877    cx.assert_editor_state(indoc!(
 6878        "
 6879        one two three
 6880        four
 6881        five
 6882        #\x20
 6883 6884        "
 6885    ));
 6886
 6887    cx.simulate_input(" 6");
 6888    cx.run_until_parked();
 6889    cx.assert_editor_state(indoc!(
 6890        "
 6891        one two three
 6892        four
 6893        five
 6894        #
 6895        # 6ˇ
 6896        "
 6897    ));
 6898}
 6899
 6900#[gpui::test]
 6901async fn test_cut_line_ends(cx: &mut TestAppContext) {
 6902    init_test(cx, |_| {});
 6903
 6904    let mut cx = EditorTestContext::new(cx).await;
 6905
 6906    cx.set_state(indoc! {"The quick brownˇ"});
 6907    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 6908    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 6909
 6910    cx.set_state(indoc! {"The emacs foxˇ"});
 6911    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 6912    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 6913
 6914    cx.set_state(indoc! {"
 6915        The quick« brownˇ»
 6916        fox jumps overˇ
 6917        the lazy dog"});
 6918    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6919    cx.assert_editor_state(indoc! {"
 6920        The quickˇ
 6921        ˇthe lazy dog"});
 6922
 6923    cx.set_state(indoc! {"
 6924        The quick« brownˇ»
 6925        fox jumps overˇ
 6926        the lazy dog"});
 6927    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 6928    cx.assert_editor_state(indoc! {"
 6929        The quickˇ
 6930        fox jumps overˇthe lazy dog"});
 6931
 6932    cx.set_state(indoc! {"
 6933        The quick« brownˇ»
 6934        fox jumps overˇ
 6935        the lazy dog"});
 6936    cx.update_editor(|e, window, cx| {
 6937        e.cut_to_end_of_line(
 6938            &CutToEndOfLine {
 6939                stop_at_newlines: true,
 6940            },
 6941            window,
 6942            cx,
 6943        )
 6944    });
 6945    cx.assert_editor_state(indoc! {"
 6946        The quickˇ
 6947        fox jumps overˇ
 6948        the lazy dog"});
 6949
 6950    cx.set_state(indoc! {"
 6951        The quick« brownˇ»
 6952        fox jumps overˇ
 6953        the lazy dog"});
 6954    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 6955    cx.assert_editor_state(indoc! {"
 6956        The quickˇ
 6957        fox jumps overˇthe lazy dog"});
 6958}
 6959
 6960#[gpui::test]
 6961async fn test_clipboard(cx: &mut TestAppContext) {
 6962    init_test(cx, |_| {});
 6963
 6964    let mut cx = EditorTestContext::new(cx).await;
 6965
 6966    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 6967    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6968    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 6969
 6970    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 6971    cx.set_state("two ˇfour ˇsix ˇ");
 6972    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6973    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 6974
 6975    // Paste again but with only two cursors. Since the number of cursors doesn't
 6976    // match the number of slices in the clipboard, the entire clipboard text
 6977    // is pasted at each cursor.
 6978    cx.set_state("ˇtwo one✅ four three six five ˇ");
 6979    cx.update_editor(|e, window, cx| {
 6980        e.handle_input("( ", window, cx);
 6981        e.paste(&Paste, window, cx);
 6982        e.handle_input(") ", window, cx);
 6983    });
 6984    cx.assert_editor_state(
 6985        &([
 6986            "( one✅ ",
 6987            "three ",
 6988            "five ) ˇtwo one✅ four three six five ( one✅ ",
 6989            "three ",
 6990            "five ) ˇ",
 6991        ]
 6992        .join("\n")),
 6993    );
 6994
 6995    // Cut with three selections, one of which is full-line.
 6996    cx.set_state(indoc! {"
 6997        1«2ˇ»3
 6998        4ˇ567
 6999        «8ˇ»9"});
 7000    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7001    cx.assert_editor_state(indoc! {"
 7002        1ˇ3
 7003        ˇ9"});
 7004
 7005    // Paste with three selections, noticing how the copied selection that was full-line
 7006    // gets inserted before the second cursor.
 7007    cx.set_state(indoc! {"
 7008        1ˇ3
 7009 7010        «oˇ»ne"});
 7011    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7012    cx.assert_editor_state(indoc! {"
 7013        12ˇ3
 7014        4567
 7015 7016        8ˇne"});
 7017
 7018    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7019    cx.set_state(indoc! {"
 7020        The quick brown
 7021        fox juˇmps over
 7022        the lazy dog"});
 7023    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7024    assert_eq!(
 7025        cx.read_from_clipboard()
 7026            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7027        Some("fox jumps over\n".to_string())
 7028    );
 7029
 7030    // Paste with three selections, noticing how the copied full-line selection is inserted
 7031    // before the empty selections but replaces the selection that is non-empty.
 7032    cx.set_state(indoc! {"
 7033        Tˇhe quick brown
 7034        «foˇ»x jumps over
 7035        tˇhe lazy dog"});
 7036    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7037    cx.assert_editor_state(indoc! {"
 7038        fox jumps over
 7039        Tˇhe quick brown
 7040        fox jumps over
 7041        ˇx jumps over
 7042        fox jumps over
 7043        tˇhe lazy dog"});
 7044}
 7045
 7046#[gpui::test]
 7047async fn test_copy_trim(cx: &mut TestAppContext) {
 7048    init_test(cx, |_| {});
 7049
 7050    let mut cx = EditorTestContext::new(cx).await;
 7051    cx.set_state(
 7052        r#"            «for selection in selections.iter() {
 7053            let mut start = selection.start;
 7054            let mut end = selection.end;
 7055            let is_entire_line = selection.is_empty();
 7056            if is_entire_line {
 7057                start = Point::new(start.row, 0);ˇ»
 7058                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7059            }
 7060        "#,
 7061    );
 7062    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7063    assert_eq!(
 7064        cx.read_from_clipboard()
 7065            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7066        Some(
 7067            "for selection in selections.iter() {
 7068            let mut start = selection.start;
 7069            let mut end = selection.end;
 7070            let is_entire_line = selection.is_empty();
 7071            if is_entire_line {
 7072                start = Point::new(start.row, 0);"
 7073                .to_string()
 7074        ),
 7075        "Regular copying preserves all indentation selected",
 7076    );
 7077    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7078    assert_eq!(
 7079        cx.read_from_clipboard()
 7080            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7081        Some(
 7082            "for selection in selections.iter() {
 7083let mut start = selection.start;
 7084let mut end = selection.end;
 7085let is_entire_line = selection.is_empty();
 7086if is_entire_line {
 7087    start = Point::new(start.row, 0);"
 7088                .to_string()
 7089        ),
 7090        "Copying with stripping should strip all leading whitespaces"
 7091    );
 7092
 7093    cx.set_state(
 7094        r#"       «     for selection in selections.iter() {
 7095            let mut start = selection.start;
 7096            let mut end = selection.end;
 7097            let is_entire_line = selection.is_empty();
 7098            if is_entire_line {
 7099                start = Point::new(start.row, 0);ˇ»
 7100                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7101            }
 7102        "#,
 7103    );
 7104    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7105    assert_eq!(
 7106        cx.read_from_clipboard()
 7107            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7108        Some(
 7109            "     for selection in selections.iter() {
 7110            let mut start = selection.start;
 7111            let mut end = selection.end;
 7112            let is_entire_line = selection.is_empty();
 7113            if is_entire_line {
 7114                start = Point::new(start.row, 0);"
 7115                .to_string()
 7116        ),
 7117        "Regular copying preserves all indentation selected",
 7118    );
 7119    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7120    assert_eq!(
 7121        cx.read_from_clipboard()
 7122            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7123        Some(
 7124            "for selection in selections.iter() {
 7125let mut start = selection.start;
 7126let mut end = selection.end;
 7127let is_entire_line = selection.is_empty();
 7128if is_entire_line {
 7129    start = Point::new(start.row, 0);"
 7130                .to_string()
 7131        ),
 7132        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7133    );
 7134
 7135    cx.set_state(
 7136        r#"       «ˇ     for selection in selections.iter() {
 7137            let mut start = selection.start;
 7138            let mut end = selection.end;
 7139            let is_entire_line = selection.is_empty();
 7140            if is_entire_line {
 7141                start = Point::new(start.row, 0);»
 7142                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7143            }
 7144        "#,
 7145    );
 7146    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7147    assert_eq!(
 7148        cx.read_from_clipboard()
 7149            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7150        Some(
 7151            "     for selection in selections.iter() {
 7152            let mut start = selection.start;
 7153            let mut end = selection.end;
 7154            let is_entire_line = selection.is_empty();
 7155            if is_entire_line {
 7156                start = Point::new(start.row, 0);"
 7157                .to_string()
 7158        ),
 7159        "Regular copying for reverse selection works the same",
 7160    );
 7161    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7162    assert_eq!(
 7163        cx.read_from_clipboard()
 7164            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7165        Some(
 7166            "for selection in selections.iter() {
 7167let mut start = selection.start;
 7168let mut end = selection.end;
 7169let is_entire_line = selection.is_empty();
 7170if is_entire_line {
 7171    start = Point::new(start.row, 0);"
 7172                .to_string()
 7173        ),
 7174        "Copying with stripping for reverse selection works the same"
 7175    );
 7176
 7177    cx.set_state(
 7178        r#"            for selection «in selections.iter() {
 7179            let mut start = selection.start;
 7180            let mut end = selection.end;
 7181            let is_entire_line = selection.is_empty();
 7182            if is_entire_line {
 7183                start = Point::new(start.row, 0);ˇ»
 7184                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7185            }
 7186        "#,
 7187    );
 7188    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7189    assert_eq!(
 7190        cx.read_from_clipboard()
 7191            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7192        Some(
 7193            "in selections.iter() {
 7194            let mut start = selection.start;
 7195            let mut end = selection.end;
 7196            let is_entire_line = selection.is_empty();
 7197            if is_entire_line {
 7198                start = Point::new(start.row, 0);"
 7199                .to_string()
 7200        ),
 7201        "When selecting past the indent, the copying works as usual",
 7202    );
 7203    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7204    assert_eq!(
 7205        cx.read_from_clipboard()
 7206            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7207        Some(
 7208            "in selections.iter() {
 7209            let mut start = selection.start;
 7210            let mut end = selection.end;
 7211            let is_entire_line = selection.is_empty();
 7212            if is_entire_line {
 7213                start = Point::new(start.row, 0);"
 7214                .to_string()
 7215        ),
 7216        "When selecting past the indent, nothing is trimmed"
 7217    );
 7218
 7219    cx.set_state(
 7220        r#"            «for selection in selections.iter() {
 7221            let mut start = selection.start;
 7222
 7223            let mut end = selection.end;
 7224            let is_entire_line = selection.is_empty();
 7225            if is_entire_line {
 7226                start = Point::new(start.row, 0);
 7227ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7228            }
 7229        "#,
 7230    );
 7231    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7232    assert_eq!(
 7233        cx.read_from_clipboard()
 7234            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7235        Some(
 7236            "for selection in selections.iter() {
 7237let mut start = selection.start;
 7238
 7239let mut end = selection.end;
 7240let is_entire_line = selection.is_empty();
 7241if is_entire_line {
 7242    start = Point::new(start.row, 0);
 7243"
 7244            .to_string()
 7245        ),
 7246        "Copying with stripping should ignore empty lines"
 7247    );
 7248}
 7249
 7250#[gpui::test]
 7251async fn test_paste_multiline(cx: &mut TestAppContext) {
 7252    init_test(cx, |_| {});
 7253
 7254    let mut cx = EditorTestContext::new(cx).await;
 7255    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7256
 7257    // Cut an indented block, without the leading whitespace.
 7258    cx.set_state(indoc! {"
 7259        const a: B = (
 7260            c(),
 7261            «d(
 7262                e,
 7263                f
 7264            )ˇ»
 7265        );
 7266    "});
 7267    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7268    cx.assert_editor_state(indoc! {"
 7269        const a: B = (
 7270            c(),
 7271            ˇ
 7272        );
 7273    "});
 7274
 7275    // Paste it at the same position.
 7276    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7277    cx.assert_editor_state(indoc! {"
 7278        const a: B = (
 7279            c(),
 7280            d(
 7281                e,
 7282                f
 7283 7284        );
 7285    "});
 7286
 7287    // Paste it at a line with a lower indent level.
 7288    cx.set_state(indoc! {"
 7289        ˇ
 7290        const a: B = (
 7291            c(),
 7292        );
 7293    "});
 7294    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7295    cx.assert_editor_state(indoc! {"
 7296        d(
 7297            e,
 7298            f
 7299 7300        const a: B = (
 7301            c(),
 7302        );
 7303    "});
 7304
 7305    // Cut an indented block, with the leading whitespace.
 7306    cx.set_state(indoc! {"
 7307        const a: B = (
 7308            c(),
 7309        «    d(
 7310                e,
 7311                f
 7312            )
 7313        ˇ»);
 7314    "});
 7315    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7316    cx.assert_editor_state(indoc! {"
 7317        const a: B = (
 7318            c(),
 7319        ˇ);
 7320    "});
 7321
 7322    // Paste it at the same position.
 7323    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7324    cx.assert_editor_state(indoc! {"
 7325        const a: B = (
 7326            c(),
 7327            d(
 7328                e,
 7329                f
 7330            )
 7331        ˇ);
 7332    "});
 7333
 7334    // Paste it at a line with a higher indent level.
 7335    cx.set_state(indoc! {"
 7336        const a: B = (
 7337            c(),
 7338            d(
 7339                e,
 7340 7341            )
 7342        );
 7343    "});
 7344    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7345    cx.assert_editor_state(indoc! {"
 7346        const a: B = (
 7347            c(),
 7348            d(
 7349                e,
 7350                f    d(
 7351                    e,
 7352                    f
 7353                )
 7354        ˇ
 7355            )
 7356        );
 7357    "});
 7358
 7359    // Copy an indented block, starting mid-line
 7360    cx.set_state(indoc! {"
 7361        const a: B = (
 7362            c(),
 7363            somethin«g(
 7364                e,
 7365                f
 7366            )ˇ»
 7367        );
 7368    "});
 7369    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7370
 7371    // Paste it on a line with a lower indent level
 7372    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7373    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7374    cx.assert_editor_state(indoc! {"
 7375        const a: B = (
 7376            c(),
 7377            something(
 7378                e,
 7379                f
 7380            )
 7381        );
 7382        g(
 7383            e,
 7384            f
 7385"});
 7386}
 7387
 7388#[gpui::test]
 7389async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7390    init_test(cx, |_| {});
 7391
 7392    cx.write_to_clipboard(ClipboardItem::new_string(
 7393        "    d(\n        e\n    );\n".into(),
 7394    ));
 7395
 7396    let mut cx = EditorTestContext::new(cx).await;
 7397    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7398
 7399    cx.set_state(indoc! {"
 7400        fn a() {
 7401            b();
 7402            if c() {
 7403                ˇ
 7404            }
 7405        }
 7406    "});
 7407
 7408    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7409    cx.assert_editor_state(indoc! {"
 7410        fn a() {
 7411            b();
 7412            if c() {
 7413                d(
 7414                    e
 7415                );
 7416        ˇ
 7417            }
 7418        }
 7419    "});
 7420
 7421    cx.set_state(indoc! {"
 7422        fn a() {
 7423            b();
 7424            ˇ
 7425        }
 7426    "});
 7427
 7428    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7429    cx.assert_editor_state(indoc! {"
 7430        fn a() {
 7431            b();
 7432            d(
 7433                e
 7434            );
 7435        ˇ
 7436        }
 7437    "});
 7438}
 7439
 7440#[gpui::test]
 7441fn test_select_all(cx: &mut TestAppContext) {
 7442    init_test(cx, |_| {});
 7443
 7444    let editor = cx.add_window(|window, cx| {
 7445        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7446        build_editor(buffer, window, cx)
 7447    });
 7448    _ = editor.update(cx, |editor, window, cx| {
 7449        editor.select_all(&SelectAll, window, cx);
 7450        assert_eq!(
 7451            editor.selections.display_ranges(cx),
 7452            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7453        );
 7454    });
 7455}
 7456
 7457#[gpui::test]
 7458fn test_select_line(cx: &mut TestAppContext) {
 7459    init_test(cx, |_| {});
 7460
 7461    let editor = cx.add_window(|window, cx| {
 7462        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7463        build_editor(buffer, window, cx)
 7464    });
 7465    _ = editor.update(cx, |editor, window, cx| {
 7466        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7467            s.select_display_ranges([
 7468                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7469                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7470                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7471                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7472            ])
 7473        });
 7474        editor.select_line(&SelectLine, window, cx);
 7475        assert_eq!(
 7476            editor.selections.display_ranges(cx),
 7477            vec![
 7478                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7479                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7480            ]
 7481        );
 7482    });
 7483
 7484    _ = editor.update(cx, |editor, window, cx| {
 7485        editor.select_line(&SelectLine, window, cx);
 7486        assert_eq!(
 7487            editor.selections.display_ranges(cx),
 7488            vec![
 7489                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7490                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7491            ]
 7492        );
 7493    });
 7494
 7495    _ = editor.update(cx, |editor, window, cx| {
 7496        editor.select_line(&SelectLine, window, cx);
 7497        assert_eq!(
 7498            editor.selections.display_ranges(cx),
 7499            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7500        );
 7501    });
 7502}
 7503
 7504#[gpui::test]
 7505async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7506    init_test(cx, |_| {});
 7507    let mut cx = EditorTestContext::new(cx).await;
 7508
 7509    #[track_caller]
 7510    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7511        cx.set_state(initial_state);
 7512        cx.update_editor(|e, window, cx| {
 7513            e.split_selection_into_lines(&Default::default(), window, cx)
 7514        });
 7515        cx.assert_editor_state(expected_state);
 7516    }
 7517
 7518    // Selection starts and ends at the middle of lines, left-to-right
 7519    test(
 7520        &mut cx,
 7521        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7522        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7523    );
 7524    // Same thing, right-to-left
 7525    test(
 7526        &mut cx,
 7527        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7528        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7529    );
 7530
 7531    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7532    test(
 7533        &mut cx,
 7534        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7535        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7536    );
 7537    // Same thing, right-to-left
 7538    test(
 7539        &mut cx,
 7540        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7541        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7542    );
 7543
 7544    // Whole buffer, left-to-right, last line ends with newline
 7545    test(
 7546        &mut cx,
 7547        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7548        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7549    );
 7550    // Same thing, right-to-left
 7551    test(
 7552        &mut cx,
 7553        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7554        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7555    );
 7556
 7557    // Starts at the end of a line, ends at the start of another
 7558    test(
 7559        &mut cx,
 7560        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7561        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7562    );
 7563}
 7564
 7565#[gpui::test]
 7566async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7567    init_test(cx, |_| {});
 7568
 7569    let editor = cx.add_window(|window, cx| {
 7570        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7571        build_editor(buffer, window, cx)
 7572    });
 7573
 7574    // setup
 7575    _ = editor.update(cx, |editor, window, cx| {
 7576        editor.fold_creases(
 7577            vec![
 7578                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7579                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7580                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7581            ],
 7582            true,
 7583            window,
 7584            cx,
 7585        );
 7586        assert_eq!(
 7587            editor.display_text(cx),
 7588            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7589        );
 7590    });
 7591
 7592    _ = editor.update(cx, |editor, window, cx| {
 7593        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7594            s.select_display_ranges([
 7595                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7596                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7597                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7598                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7599            ])
 7600        });
 7601        editor.split_selection_into_lines(&Default::default(), window, cx);
 7602        assert_eq!(
 7603            editor.display_text(cx),
 7604            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7605        );
 7606    });
 7607    EditorTestContext::for_editor(editor, cx)
 7608        .await
 7609        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7610
 7611    _ = editor.update(cx, |editor, window, cx| {
 7612        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7613            s.select_display_ranges([
 7614                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7615            ])
 7616        });
 7617        editor.split_selection_into_lines(&Default::default(), window, cx);
 7618        assert_eq!(
 7619            editor.display_text(cx),
 7620            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7621        );
 7622        assert_eq!(
 7623            editor.selections.display_ranges(cx),
 7624            [
 7625                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7626                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7627                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7628                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7629                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7630                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7631                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7632            ]
 7633        );
 7634    });
 7635    EditorTestContext::for_editor(editor, cx)
 7636        .await
 7637        .assert_editor_state(
 7638            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7639        );
 7640}
 7641
 7642#[gpui::test]
 7643async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7644    init_test(cx, |_| {});
 7645
 7646    let mut cx = EditorTestContext::new(cx).await;
 7647
 7648    cx.set_state(indoc!(
 7649        r#"abc
 7650           defˇghi
 7651
 7652           jk
 7653           nlmo
 7654           "#
 7655    ));
 7656
 7657    cx.update_editor(|editor, window, cx| {
 7658        editor.add_selection_above(&Default::default(), window, cx);
 7659    });
 7660
 7661    cx.assert_editor_state(indoc!(
 7662        r#"abcˇ
 7663           defˇghi
 7664
 7665           jk
 7666           nlmo
 7667           "#
 7668    ));
 7669
 7670    cx.update_editor(|editor, window, cx| {
 7671        editor.add_selection_above(&Default::default(), window, cx);
 7672    });
 7673
 7674    cx.assert_editor_state(indoc!(
 7675        r#"abcˇ
 7676            defˇghi
 7677
 7678            jk
 7679            nlmo
 7680            "#
 7681    ));
 7682
 7683    cx.update_editor(|editor, window, cx| {
 7684        editor.add_selection_below(&Default::default(), window, cx);
 7685    });
 7686
 7687    cx.assert_editor_state(indoc!(
 7688        r#"abc
 7689           defˇghi
 7690
 7691           jk
 7692           nlmo
 7693           "#
 7694    ));
 7695
 7696    cx.update_editor(|editor, window, cx| {
 7697        editor.undo_selection(&Default::default(), window, cx);
 7698    });
 7699
 7700    cx.assert_editor_state(indoc!(
 7701        r#"abcˇ
 7702           defˇghi
 7703
 7704           jk
 7705           nlmo
 7706           "#
 7707    ));
 7708
 7709    cx.update_editor(|editor, window, cx| {
 7710        editor.redo_selection(&Default::default(), window, cx);
 7711    });
 7712
 7713    cx.assert_editor_state(indoc!(
 7714        r#"abc
 7715           defˇghi
 7716
 7717           jk
 7718           nlmo
 7719           "#
 7720    ));
 7721
 7722    cx.update_editor(|editor, window, cx| {
 7723        editor.add_selection_below(&Default::default(), window, cx);
 7724    });
 7725
 7726    cx.assert_editor_state(indoc!(
 7727        r#"abc
 7728           defˇghi
 7729           ˇ
 7730           jk
 7731           nlmo
 7732           "#
 7733    ));
 7734
 7735    cx.update_editor(|editor, window, cx| {
 7736        editor.add_selection_below(&Default::default(), window, cx);
 7737    });
 7738
 7739    cx.assert_editor_state(indoc!(
 7740        r#"abc
 7741           defˇghi
 7742           ˇ
 7743           jkˇ
 7744           nlmo
 7745           "#
 7746    ));
 7747
 7748    cx.update_editor(|editor, window, cx| {
 7749        editor.add_selection_below(&Default::default(), window, cx);
 7750    });
 7751
 7752    cx.assert_editor_state(indoc!(
 7753        r#"abc
 7754           defˇghi
 7755           ˇ
 7756           jkˇ
 7757           nlmˇo
 7758           "#
 7759    ));
 7760
 7761    cx.update_editor(|editor, window, cx| {
 7762        editor.add_selection_below(&Default::default(), window, cx);
 7763    });
 7764
 7765    cx.assert_editor_state(indoc!(
 7766        r#"abc
 7767           defˇghi
 7768           ˇ
 7769           jkˇ
 7770           nlmˇo
 7771           ˇ"#
 7772    ));
 7773
 7774    // change selections
 7775    cx.set_state(indoc!(
 7776        r#"abc
 7777           def«ˇg»hi
 7778
 7779           jk
 7780           nlmo
 7781           "#
 7782    ));
 7783
 7784    cx.update_editor(|editor, window, cx| {
 7785        editor.add_selection_below(&Default::default(), window, cx);
 7786    });
 7787
 7788    cx.assert_editor_state(indoc!(
 7789        r#"abc
 7790           def«ˇg»hi
 7791
 7792           jk
 7793           nlm«ˇo»
 7794           "#
 7795    ));
 7796
 7797    cx.update_editor(|editor, window, cx| {
 7798        editor.add_selection_below(&Default::default(), window, cx);
 7799    });
 7800
 7801    cx.assert_editor_state(indoc!(
 7802        r#"abc
 7803           def«ˇg»hi
 7804
 7805           jk
 7806           nlm«ˇo»
 7807           "#
 7808    ));
 7809
 7810    cx.update_editor(|editor, window, cx| {
 7811        editor.add_selection_above(&Default::default(), window, cx);
 7812    });
 7813
 7814    cx.assert_editor_state(indoc!(
 7815        r#"abc
 7816           def«ˇg»hi
 7817
 7818           jk
 7819           nlmo
 7820           "#
 7821    ));
 7822
 7823    cx.update_editor(|editor, window, cx| {
 7824        editor.add_selection_above(&Default::default(), window, cx);
 7825    });
 7826
 7827    cx.assert_editor_state(indoc!(
 7828        r#"abc
 7829           def«ˇg»hi
 7830
 7831           jk
 7832           nlmo
 7833           "#
 7834    ));
 7835
 7836    // Change selections again
 7837    cx.set_state(indoc!(
 7838        r#"a«bc
 7839           defgˇ»hi
 7840
 7841           jk
 7842           nlmo
 7843           "#
 7844    ));
 7845
 7846    cx.update_editor(|editor, window, cx| {
 7847        editor.add_selection_below(&Default::default(), window, cx);
 7848    });
 7849
 7850    cx.assert_editor_state(indoc!(
 7851        r#"a«bcˇ»
 7852           d«efgˇ»hi
 7853
 7854           j«kˇ»
 7855           nlmo
 7856           "#
 7857    ));
 7858
 7859    cx.update_editor(|editor, window, cx| {
 7860        editor.add_selection_below(&Default::default(), window, cx);
 7861    });
 7862    cx.assert_editor_state(indoc!(
 7863        r#"a«bcˇ»
 7864           d«efgˇ»hi
 7865
 7866           j«kˇ»
 7867           n«lmoˇ»
 7868           "#
 7869    ));
 7870    cx.update_editor(|editor, window, cx| {
 7871        editor.add_selection_above(&Default::default(), window, cx);
 7872    });
 7873
 7874    cx.assert_editor_state(indoc!(
 7875        r#"a«bcˇ»
 7876           d«efgˇ»hi
 7877
 7878           j«kˇ»
 7879           nlmo
 7880           "#
 7881    ));
 7882
 7883    // Change selections again
 7884    cx.set_state(indoc!(
 7885        r#"abc
 7886           d«ˇefghi
 7887
 7888           jk
 7889           nlm»o
 7890           "#
 7891    ));
 7892
 7893    cx.update_editor(|editor, window, cx| {
 7894        editor.add_selection_above(&Default::default(), window, cx);
 7895    });
 7896
 7897    cx.assert_editor_state(indoc!(
 7898        r#"a«ˇbc»
 7899           d«ˇef»ghi
 7900
 7901           j«ˇk»
 7902           n«ˇlm»o
 7903           "#
 7904    ));
 7905
 7906    cx.update_editor(|editor, window, cx| {
 7907        editor.add_selection_below(&Default::default(), window, cx);
 7908    });
 7909
 7910    cx.assert_editor_state(indoc!(
 7911        r#"abc
 7912           d«ˇef»ghi
 7913
 7914           j«ˇk»
 7915           n«ˇlm»o
 7916           "#
 7917    ));
 7918}
 7919
 7920#[gpui::test]
 7921async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 7922    init_test(cx, |_| {});
 7923    let mut cx = EditorTestContext::new(cx).await;
 7924
 7925    cx.set_state(indoc!(
 7926        r#"line onˇe
 7927           liˇne two
 7928           line three
 7929           line four"#
 7930    ));
 7931
 7932    cx.update_editor(|editor, window, cx| {
 7933        editor.add_selection_below(&Default::default(), window, cx);
 7934    });
 7935
 7936    // test multiple cursors expand in the same direction
 7937    cx.assert_editor_state(indoc!(
 7938        r#"line onˇe
 7939           liˇne twˇo
 7940           liˇne three
 7941           line four"#
 7942    ));
 7943
 7944    cx.update_editor(|editor, window, cx| {
 7945        editor.add_selection_below(&Default::default(), window, cx);
 7946    });
 7947
 7948    cx.update_editor(|editor, window, cx| {
 7949        editor.add_selection_below(&Default::default(), window, cx);
 7950    });
 7951
 7952    // test multiple cursors expand below overflow
 7953    cx.assert_editor_state(indoc!(
 7954        r#"line onˇe
 7955           liˇne twˇo
 7956           liˇne thˇree
 7957           liˇne foˇur"#
 7958    ));
 7959
 7960    cx.update_editor(|editor, window, cx| {
 7961        editor.add_selection_above(&Default::default(), window, cx);
 7962    });
 7963
 7964    // test multiple cursors retrieves back correctly
 7965    cx.assert_editor_state(indoc!(
 7966        r#"line onˇe
 7967           liˇne twˇo
 7968           liˇne thˇree
 7969           line four"#
 7970    ));
 7971
 7972    cx.update_editor(|editor, window, cx| {
 7973        editor.add_selection_above(&Default::default(), window, cx);
 7974    });
 7975
 7976    cx.update_editor(|editor, window, cx| {
 7977        editor.add_selection_above(&Default::default(), window, cx);
 7978    });
 7979
 7980    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 7981    cx.assert_editor_state(indoc!(
 7982        r#"liˇne onˇe
 7983           liˇne two
 7984           line three
 7985           line four"#
 7986    ));
 7987
 7988    cx.update_editor(|editor, window, cx| {
 7989        editor.undo_selection(&Default::default(), window, cx);
 7990    });
 7991
 7992    // test undo
 7993    cx.assert_editor_state(indoc!(
 7994        r#"line onˇe
 7995           liˇne twˇo
 7996           line three
 7997           line four"#
 7998    ));
 7999
 8000    cx.update_editor(|editor, window, cx| {
 8001        editor.redo_selection(&Default::default(), window, cx);
 8002    });
 8003
 8004    // test redo
 8005    cx.assert_editor_state(indoc!(
 8006        r#"liˇne onˇe
 8007           liˇne two
 8008           line three
 8009           line four"#
 8010    ));
 8011
 8012    cx.set_state(indoc!(
 8013        r#"abcd
 8014           ef«ghˇ»
 8015           ijkl
 8016           «mˇ»nop"#
 8017    ));
 8018
 8019    cx.update_editor(|editor, window, cx| {
 8020        editor.add_selection_above(&Default::default(), window, cx);
 8021    });
 8022
 8023    // test multiple selections expand in the same direction
 8024    cx.assert_editor_state(indoc!(
 8025        r#"ab«cdˇ»
 8026           ef«ghˇ»
 8027           «iˇ»jkl
 8028           «mˇ»nop"#
 8029    ));
 8030
 8031    cx.update_editor(|editor, window, cx| {
 8032        editor.add_selection_above(&Default::default(), window, cx);
 8033    });
 8034
 8035    // test multiple selection upward overflow
 8036    cx.assert_editor_state(indoc!(
 8037        r#"ab«cdˇ»
 8038           «eˇ»f«ghˇ»
 8039           «iˇ»jkl
 8040           «mˇ»nop"#
 8041    ));
 8042
 8043    cx.update_editor(|editor, window, cx| {
 8044        editor.add_selection_below(&Default::default(), window, cx);
 8045    });
 8046
 8047    // test multiple selection retrieves back correctly
 8048    cx.assert_editor_state(indoc!(
 8049        r#"abcd
 8050           ef«ghˇ»
 8051           «iˇ»jkl
 8052           «mˇ»nop"#
 8053    ));
 8054
 8055    cx.update_editor(|editor, window, cx| {
 8056        editor.add_selection_below(&Default::default(), window, cx);
 8057    });
 8058
 8059    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8060    cx.assert_editor_state(indoc!(
 8061        r#"abcd
 8062           ef«ghˇ»
 8063           ij«klˇ»
 8064           «mˇ»nop"#
 8065    ));
 8066
 8067    cx.update_editor(|editor, window, cx| {
 8068        editor.undo_selection(&Default::default(), window, cx);
 8069    });
 8070
 8071    // test undo
 8072    cx.assert_editor_state(indoc!(
 8073        r#"abcd
 8074           ef«ghˇ»
 8075           «iˇ»jkl
 8076           «mˇ»nop"#
 8077    ));
 8078
 8079    cx.update_editor(|editor, window, cx| {
 8080        editor.redo_selection(&Default::default(), window, cx);
 8081    });
 8082
 8083    // test redo
 8084    cx.assert_editor_state(indoc!(
 8085        r#"abcd
 8086           ef«ghˇ»
 8087           ij«klˇ»
 8088           «mˇ»nop"#
 8089    ));
 8090}
 8091
 8092#[gpui::test]
 8093async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8094    init_test(cx, |_| {});
 8095    let mut cx = EditorTestContext::new(cx).await;
 8096
 8097    cx.set_state(indoc!(
 8098        r#"line onˇe
 8099           liˇne two
 8100           line three
 8101           line four"#
 8102    ));
 8103
 8104    cx.update_editor(|editor, window, cx| {
 8105        editor.add_selection_below(&Default::default(), window, cx);
 8106        editor.add_selection_below(&Default::default(), window, cx);
 8107        editor.add_selection_below(&Default::default(), window, cx);
 8108    });
 8109
 8110    // initial state with two multi cursor groups
 8111    cx.assert_editor_state(indoc!(
 8112        r#"line onˇe
 8113           liˇne twˇo
 8114           liˇne thˇree
 8115           liˇne foˇur"#
 8116    ));
 8117
 8118    // add single cursor in middle - simulate opt click
 8119    cx.update_editor(|editor, window, cx| {
 8120        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8121        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8122        editor.end_selection(window, cx);
 8123    });
 8124
 8125    cx.assert_editor_state(indoc!(
 8126        r#"line onˇe
 8127           liˇne twˇo
 8128           liˇneˇ thˇree
 8129           liˇne foˇur"#
 8130    ));
 8131
 8132    cx.update_editor(|editor, window, cx| {
 8133        editor.add_selection_above(&Default::default(), window, cx);
 8134    });
 8135
 8136    // test new added selection expands above and existing selection shrinks
 8137    cx.assert_editor_state(indoc!(
 8138        r#"line onˇe
 8139           liˇneˇ twˇo
 8140           liˇneˇ thˇree
 8141           line four"#
 8142    ));
 8143
 8144    cx.update_editor(|editor, window, cx| {
 8145        editor.add_selection_above(&Default::default(), window, cx);
 8146    });
 8147
 8148    // test new added selection expands above and existing selection shrinks
 8149    cx.assert_editor_state(indoc!(
 8150        r#"lineˇ onˇe
 8151           liˇneˇ twˇo
 8152           lineˇ three
 8153           line four"#
 8154    ));
 8155
 8156    // intial state with two selection groups
 8157    cx.set_state(indoc!(
 8158        r#"abcd
 8159           ef«ghˇ»
 8160           ijkl
 8161           «mˇ»nop"#
 8162    ));
 8163
 8164    cx.update_editor(|editor, window, cx| {
 8165        editor.add_selection_above(&Default::default(), window, cx);
 8166        editor.add_selection_above(&Default::default(), window, cx);
 8167    });
 8168
 8169    cx.assert_editor_state(indoc!(
 8170        r#"ab«cdˇ»
 8171           «eˇ»f«ghˇ»
 8172           «iˇ»jkl
 8173           «mˇ»nop"#
 8174    ));
 8175
 8176    // add single selection in middle - simulate opt drag
 8177    cx.update_editor(|editor, window, cx| {
 8178        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8179        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8180        editor.update_selection(
 8181            DisplayPoint::new(DisplayRow(2), 4),
 8182            0,
 8183            gpui::Point::<f32>::default(),
 8184            window,
 8185            cx,
 8186        );
 8187        editor.end_selection(window, cx);
 8188    });
 8189
 8190    cx.assert_editor_state(indoc!(
 8191        r#"ab«cdˇ»
 8192           «eˇ»f«ghˇ»
 8193           «iˇ»jk«lˇ»
 8194           «mˇ»nop"#
 8195    ));
 8196
 8197    cx.update_editor(|editor, window, cx| {
 8198        editor.add_selection_below(&Default::default(), window, cx);
 8199    });
 8200
 8201    // test new added selection expands below, others shrinks from above
 8202    cx.assert_editor_state(indoc!(
 8203        r#"abcd
 8204           ef«ghˇ»
 8205           «iˇ»jk«lˇ»
 8206           «mˇ»no«pˇ»"#
 8207    ));
 8208}
 8209
 8210#[gpui::test]
 8211async fn test_select_next(cx: &mut TestAppContext) {
 8212    init_test(cx, |_| {});
 8213
 8214    let mut cx = EditorTestContext::new(cx).await;
 8215    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8216
 8217    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8218        .unwrap();
 8219    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8220
 8221    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8222        .unwrap();
 8223    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8224
 8225    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8226    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8227
 8228    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8229    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8230
 8231    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8232        .unwrap();
 8233    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8234
 8235    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8236        .unwrap();
 8237    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8238
 8239    // Test selection direction should be preserved
 8240    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8241
 8242    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8243        .unwrap();
 8244    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8245}
 8246
 8247#[gpui::test]
 8248async fn test_select_all_matches(cx: &mut TestAppContext) {
 8249    init_test(cx, |_| {});
 8250
 8251    let mut cx = EditorTestContext::new(cx).await;
 8252
 8253    // Test caret-only selections
 8254    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8255    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8256        .unwrap();
 8257    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8258
 8259    // Test left-to-right selections
 8260    cx.set_state("abc\n«abcˇ»\nabc");
 8261    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8262        .unwrap();
 8263    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8264
 8265    // Test right-to-left selections
 8266    cx.set_state("abc\n«ˇabc»\nabc");
 8267    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8268        .unwrap();
 8269    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8270
 8271    // Test selecting whitespace with caret selection
 8272    cx.set_state("abc\nˇ   abc\nabc");
 8273    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8274        .unwrap();
 8275    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8276
 8277    // Test selecting whitespace with left-to-right selection
 8278    cx.set_state("abc\n«ˇ  »abc\nabc");
 8279    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8280        .unwrap();
 8281    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8282
 8283    // Test no matches with right-to-left selection
 8284    cx.set_state("abc\n«  ˇ»abc\nabc");
 8285    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8286        .unwrap();
 8287    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8288
 8289    // Test with a single word and clip_at_line_ends=true (#29823)
 8290    cx.set_state("aˇbc");
 8291    cx.update_editor(|e, window, cx| {
 8292        e.set_clip_at_line_ends(true, cx);
 8293        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8294        e.set_clip_at_line_ends(false, cx);
 8295    });
 8296    cx.assert_editor_state("«abcˇ»");
 8297}
 8298
 8299#[gpui::test]
 8300async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8301    init_test(cx, |_| {});
 8302
 8303    let mut cx = EditorTestContext::new(cx).await;
 8304
 8305    let large_body_1 = "\nd".repeat(200);
 8306    let large_body_2 = "\ne".repeat(200);
 8307
 8308    cx.set_state(&format!(
 8309        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8310    ));
 8311    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8312        let scroll_position = editor.scroll_position(cx);
 8313        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8314        scroll_position
 8315    });
 8316
 8317    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8318        .unwrap();
 8319    cx.assert_editor_state(&format!(
 8320        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8321    ));
 8322    let scroll_position_after_selection =
 8323        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8324    assert_eq!(
 8325        initial_scroll_position, scroll_position_after_selection,
 8326        "Scroll position should not change after selecting all matches"
 8327    );
 8328}
 8329
 8330#[gpui::test]
 8331async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8332    init_test(cx, |_| {});
 8333
 8334    let mut cx = EditorLspTestContext::new_rust(
 8335        lsp::ServerCapabilities {
 8336            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8337            ..Default::default()
 8338        },
 8339        cx,
 8340    )
 8341    .await;
 8342
 8343    cx.set_state(indoc! {"
 8344        line 1
 8345        line 2
 8346        linˇe 3
 8347        line 4
 8348        line 5
 8349    "});
 8350
 8351    // Make an edit
 8352    cx.update_editor(|editor, window, cx| {
 8353        editor.handle_input("X", window, cx);
 8354    });
 8355
 8356    // Move cursor to a different position
 8357    cx.update_editor(|editor, window, cx| {
 8358        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8359            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8360        });
 8361    });
 8362
 8363    cx.assert_editor_state(indoc! {"
 8364        line 1
 8365        line 2
 8366        linXe 3
 8367        line 4
 8368        liˇne 5
 8369    "});
 8370
 8371    cx.lsp
 8372        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8373            Ok(Some(vec![lsp::TextEdit::new(
 8374                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8375                "PREFIX ".to_string(),
 8376            )]))
 8377        });
 8378
 8379    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8380        .unwrap()
 8381        .await
 8382        .unwrap();
 8383
 8384    cx.assert_editor_state(indoc! {"
 8385        PREFIX line 1
 8386        line 2
 8387        linXe 3
 8388        line 4
 8389        liˇne 5
 8390    "});
 8391
 8392    // Undo formatting
 8393    cx.update_editor(|editor, window, cx| {
 8394        editor.undo(&Default::default(), window, cx);
 8395    });
 8396
 8397    // Verify cursor moved back to position after edit
 8398    cx.assert_editor_state(indoc! {"
 8399        line 1
 8400        line 2
 8401        linXˇe 3
 8402        line 4
 8403        line 5
 8404    "});
 8405}
 8406
 8407#[gpui::test]
 8408async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8409    init_test(cx, |_| {});
 8410
 8411    let mut cx = EditorTestContext::new(cx).await;
 8412
 8413    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8414    cx.update_editor(|editor, window, cx| {
 8415        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8416    });
 8417
 8418    cx.set_state(indoc! {"
 8419        line 1
 8420        line 2
 8421        linˇe 3
 8422        line 4
 8423        line 5
 8424        line 6
 8425        line 7
 8426        line 8
 8427        line 9
 8428        line 10
 8429    "});
 8430
 8431    let snapshot = cx.buffer_snapshot();
 8432    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8433
 8434    cx.update(|_, cx| {
 8435        provider.update(cx, |provider, _| {
 8436            provider.set_edit_prediction(Some(edit_prediction::EditPrediction::Local {
 8437                id: None,
 8438                edits: vec![(edit_position..edit_position, "X".into())],
 8439                edit_preview: None,
 8440            }))
 8441        })
 8442    });
 8443
 8444    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8445    cx.update_editor(|editor, window, cx| {
 8446        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8447    });
 8448
 8449    cx.assert_editor_state(indoc! {"
 8450        line 1
 8451        line 2
 8452        lineXˇ 3
 8453        line 4
 8454        line 5
 8455        line 6
 8456        line 7
 8457        line 8
 8458        line 9
 8459        line 10
 8460    "});
 8461
 8462    cx.update_editor(|editor, window, cx| {
 8463        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8464            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8465        });
 8466    });
 8467
 8468    cx.assert_editor_state(indoc! {"
 8469        line 1
 8470        line 2
 8471        lineX 3
 8472        line 4
 8473        line 5
 8474        line 6
 8475        line 7
 8476        line 8
 8477        line 9
 8478        liˇne 10
 8479    "});
 8480
 8481    cx.update_editor(|editor, window, cx| {
 8482        editor.undo(&Default::default(), window, cx);
 8483    });
 8484
 8485    cx.assert_editor_state(indoc! {"
 8486        line 1
 8487        line 2
 8488        lineˇ 3
 8489        line 4
 8490        line 5
 8491        line 6
 8492        line 7
 8493        line 8
 8494        line 9
 8495        line 10
 8496    "});
 8497}
 8498
 8499#[gpui::test]
 8500async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8501    init_test(cx, |_| {});
 8502
 8503    let mut cx = EditorTestContext::new(cx).await;
 8504    cx.set_state(
 8505        r#"let foo = 2;
 8506lˇet foo = 2;
 8507let fooˇ = 2;
 8508let foo = 2;
 8509let foo = ˇ2;"#,
 8510    );
 8511
 8512    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8513        .unwrap();
 8514    cx.assert_editor_state(
 8515        r#"let foo = 2;
 8516«letˇ» foo = 2;
 8517let «fooˇ» = 2;
 8518let foo = 2;
 8519let foo = «2ˇ»;"#,
 8520    );
 8521
 8522    // noop for multiple selections with different contents
 8523    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8524        .unwrap();
 8525    cx.assert_editor_state(
 8526        r#"let foo = 2;
 8527«letˇ» foo = 2;
 8528let «fooˇ» = 2;
 8529let foo = 2;
 8530let foo = «2ˇ»;"#,
 8531    );
 8532
 8533    // Test last selection direction should be preserved
 8534    cx.set_state(
 8535        r#"let foo = 2;
 8536let foo = 2;
 8537let «fooˇ» = 2;
 8538let «ˇfoo» = 2;
 8539let foo = 2;"#,
 8540    );
 8541
 8542    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8543        .unwrap();
 8544    cx.assert_editor_state(
 8545        r#"let foo = 2;
 8546let foo = 2;
 8547let «fooˇ» = 2;
 8548let «ˇfoo» = 2;
 8549let «ˇfoo» = 2;"#,
 8550    );
 8551}
 8552
 8553#[gpui::test]
 8554async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8555    init_test(cx, |_| {});
 8556
 8557    let mut cx =
 8558        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8559
 8560    cx.assert_editor_state(indoc! {"
 8561        ˇbbb
 8562        ccc
 8563
 8564        bbb
 8565        ccc
 8566        "});
 8567    cx.dispatch_action(SelectPrevious::default());
 8568    cx.assert_editor_state(indoc! {"
 8569                «bbbˇ»
 8570                ccc
 8571
 8572                bbb
 8573                ccc
 8574                "});
 8575    cx.dispatch_action(SelectPrevious::default());
 8576    cx.assert_editor_state(indoc! {"
 8577                «bbbˇ»
 8578                ccc
 8579
 8580                «bbbˇ»
 8581                ccc
 8582                "});
 8583}
 8584
 8585#[gpui::test]
 8586async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8587    init_test(cx, |_| {});
 8588
 8589    let mut cx = EditorTestContext::new(cx).await;
 8590    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8591
 8592    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8593        .unwrap();
 8594    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8595
 8596    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8597        .unwrap();
 8598    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8599
 8600    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8601    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8602
 8603    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8604    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8605
 8606    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8607        .unwrap();
 8608    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8609
 8610    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8611        .unwrap();
 8612    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8613}
 8614
 8615#[gpui::test]
 8616async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8617    init_test(cx, |_| {});
 8618
 8619    let mut cx = EditorTestContext::new(cx).await;
 8620    cx.set_state("");
 8621
 8622    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8623        .unwrap();
 8624    cx.assert_editor_state("«aˇ»");
 8625    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8626        .unwrap();
 8627    cx.assert_editor_state("«aˇ»");
 8628}
 8629
 8630#[gpui::test]
 8631async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8632    init_test(cx, |_| {});
 8633
 8634    let mut cx = EditorTestContext::new(cx).await;
 8635    cx.set_state(
 8636        r#"let foo = 2;
 8637lˇet foo = 2;
 8638let fooˇ = 2;
 8639let foo = 2;
 8640let foo = ˇ2;"#,
 8641    );
 8642
 8643    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8644        .unwrap();
 8645    cx.assert_editor_state(
 8646        r#"let foo = 2;
 8647«letˇ» foo = 2;
 8648let «fooˇ» = 2;
 8649let foo = 2;
 8650let foo = «2ˇ»;"#,
 8651    );
 8652
 8653    // noop for multiple selections with different contents
 8654    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8655        .unwrap();
 8656    cx.assert_editor_state(
 8657        r#"let foo = 2;
 8658«letˇ» foo = 2;
 8659let «fooˇ» = 2;
 8660let foo = 2;
 8661let foo = «2ˇ»;"#,
 8662    );
 8663}
 8664
 8665#[gpui::test]
 8666async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8667    init_test(cx, |_| {});
 8668
 8669    let mut cx = EditorTestContext::new(cx).await;
 8670    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8671
 8672    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8673        .unwrap();
 8674    // selection direction is preserved
 8675    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8676
 8677    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8678        .unwrap();
 8679    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8680
 8681    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8682    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8683
 8684    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8685    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8686
 8687    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8688        .unwrap();
 8689    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8690
 8691    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8692        .unwrap();
 8693    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8694}
 8695
 8696#[gpui::test]
 8697async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8698    init_test(cx, |_| {});
 8699
 8700    let language = Arc::new(Language::new(
 8701        LanguageConfig::default(),
 8702        Some(tree_sitter_rust::LANGUAGE.into()),
 8703    ));
 8704
 8705    let text = r#"
 8706        use mod1::mod2::{mod3, mod4};
 8707
 8708        fn fn_1(param1: bool, param2: &str) {
 8709            let var1 = "text";
 8710        }
 8711    "#
 8712    .unindent();
 8713
 8714    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8715    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8716    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8717
 8718    editor
 8719        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8720        .await;
 8721
 8722    editor.update_in(cx, |editor, window, cx| {
 8723        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8724            s.select_display_ranges([
 8725                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8726                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8727                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8728            ]);
 8729        });
 8730        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8731    });
 8732    editor.update(cx, |editor, cx| {
 8733        assert_text_with_selections(
 8734            editor,
 8735            indoc! {r#"
 8736                use mod1::mod2::{mod3, «mod4ˇ»};
 8737
 8738                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8739                    let var1 = "«ˇtext»";
 8740                }
 8741            "#},
 8742            cx,
 8743        );
 8744    });
 8745
 8746    editor.update_in(cx, |editor, window, cx| {
 8747        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8748    });
 8749    editor.update(cx, |editor, cx| {
 8750        assert_text_with_selections(
 8751            editor,
 8752            indoc! {r#"
 8753                use mod1::mod2::«{mod3, mod4}ˇ»;
 8754
 8755                «ˇfn fn_1(param1: bool, param2: &str) {
 8756                    let var1 = "text";
 8757 8758            "#},
 8759            cx,
 8760        );
 8761    });
 8762
 8763    editor.update_in(cx, |editor, window, cx| {
 8764        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8765    });
 8766    assert_eq!(
 8767        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8768        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8769    );
 8770
 8771    // Trying to expand the selected syntax node one more time has no effect.
 8772    editor.update_in(cx, |editor, window, cx| {
 8773        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8774    });
 8775    assert_eq!(
 8776        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8777        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8778    );
 8779
 8780    editor.update_in(cx, |editor, window, cx| {
 8781        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8782    });
 8783    editor.update(cx, |editor, cx| {
 8784        assert_text_with_selections(
 8785            editor,
 8786            indoc! {r#"
 8787                use mod1::mod2::«{mod3, mod4}ˇ»;
 8788
 8789                «ˇfn fn_1(param1: bool, param2: &str) {
 8790                    let var1 = "text";
 8791 8792            "#},
 8793            cx,
 8794        );
 8795    });
 8796
 8797    editor.update_in(cx, |editor, window, cx| {
 8798        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8799    });
 8800    editor.update(cx, |editor, cx| {
 8801        assert_text_with_selections(
 8802            editor,
 8803            indoc! {r#"
 8804                use mod1::mod2::{mod3, «mod4ˇ»};
 8805
 8806                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8807                    let var1 = "«ˇtext»";
 8808                }
 8809            "#},
 8810            cx,
 8811        );
 8812    });
 8813
 8814    editor.update_in(cx, |editor, window, cx| {
 8815        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8816    });
 8817    editor.update(cx, |editor, cx| {
 8818        assert_text_with_selections(
 8819            editor,
 8820            indoc! {r#"
 8821                use mod1::mod2::{mod3, moˇd4};
 8822
 8823                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8824                    let var1 = "teˇxt";
 8825                }
 8826            "#},
 8827            cx,
 8828        );
 8829    });
 8830
 8831    // Trying to shrink the selected syntax node one more time has no effect.
 8832    editor.update_in(cx, |editor, window, cx| {
 8833        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8834    });
 8835    editor.update_in(cx, |editor, _, cx| {
 8836        assert_text_with_selections(
 8837            editor,
 8838            indoc! {r#"
 8839                use mod1::mod2::{mod3, moˇd4};
 8840
 8841                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8842                    let var1 = "teˇxt";
 8843                }
 8844            "#},
 8845            cx,
 8846        );
 8847    });
 8848
 8849    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8850    // a fold.
 8851    editor.update_in(cx, |editor, window, cx| {
 8852        editor.fold_creases(
 8853            vec![
 8854                Crease::simple(
 8855                    Point::new(0, 21)..Point::new(0, 24),
 8856                    FoldPlaceholder::test(),
 8857                ),
 8858                Crease::simple(
 8859                    Point::new(3, 20)..Point::new(3, 22),
 8860                    FoldPlaceholder::test(),
 8861                ),
 8862            ],
 8863            true,
 8864            window,
 8865            cx,
 8866        );
 8867        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8868    });
 8869    editor.update(cx, |editor, cx| {
 8870        assert_text_with_selections(
 8871            editor,
 8872            indoc! {r#"
 8873                use mod1::mod2::«{mod3, mod4}ˇ»;
 8874
 8875                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8876                    let var1 = "«ˇtext»";
 8877                }
 8878            "#},
 8879            cx,
 8880        );
 8881    });
 8882}
 8883
 8884#[gpui::test]
 8885async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8886    init_test(cx, |_| {});
 8887
 8888    let language = Arc::new(Language::new(
 8889        LanguageConfig::default(),
 8890        Some(tree_sitter_rust::LANGUAGE.into()),
 8891    ));
 8892
 8893    let text = "let a = 2;";
 8894
 8895    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8896    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8897    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8898
 8899    editor
 8900        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8901        .await;
 8902
 8903    // Test case 1: Cursor at end of word
 8904    editor.update_in(cx, |editor, window, cx| {
 8905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8906            s.select_display_ranges([
 8907                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 8908            ]);
 8909        });
 8910    });
 8911    editor.update(cx, |editor, cx| {
 8912        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 8913    });
 8914    editor.update_in(cx, |editor, window, cx| {
 8915        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8916    });
 8917    editor.update(cx, |editor, cx| {
 8918        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 8919    });
 8920    editor.update_in(cx, |editor, window, cx| {
 8921        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8922    });
 8923    editor.update(cx, |editor, cx| {
 8924        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8925    });
 8926
 8927    // Test case 2: Cursor at end of statement
 8928    editor.update_in(cx, |editor, window, cx| {
 8929        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8930            s.select_display_ranges([
 8931                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 8932            ]);
 8933        });
 8934    });
 8935    editor.update(cx, |editor, cx| {
 8936        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 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(editor, "«ˇlet a = 2;»", cx);
 8943    });
 8944}
 8945
 8946#[gpui::test]
 8947async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 8948    init_test(cx, |_| {});
 8949
 8950    let language = Arc::new(Language::new(
 8951        LanguageConfig {
 8952            name: "JavaScript".into(),
 8953            ..Default::default()
 8954        },
 8955        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8956    ));
 8957
 8958    let text = r#"
 8959        let a = {
 8960            key: "value",
 8961        };
 8962    "#
 8963    .unindent();
 8964
 8965    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8966    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8967    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8968
 8969    editor
 8970        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8971        .await;
 8972
 8973    // Test case 1: Cursor after '{'
 8974    editor.update_in(cx, |editor, window, cx| {
 8975        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8976            s.select_display_ranges([
 8977                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 8978            ]);
 8979        });
 8980    });
 8981    editor.update(cx, |editor, cx| {
 8982        assert_text_with_selections(
 8983            editor,
 8984            indoc! {r#"
 8985                let a = {ˇ
 8986                    key: "value",
 8987                };
 8988            "#},
 8989            cx,
 8990        );
 8991    });
 8992    editor.update_in(cx, |editor, window, cx| {
 8993        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8994    });
 8995    editor.update(cx, |editor, cx| {
 8996        assert_text_with_selections(
 8997            editor,
 8998            indoc! {r#"
 8999                let a = «ˇ{
 9000                    key: "value",
 9001                }»;
 9002            "#},
 9003            cx,
 9004        );
 9005    });
 9006
 9007    // Test case 2: Cursor after ':'
 9008    editor.update_in(cx, |editor, window, cx| {
 9009        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9010            s.select_display_ranges([
 9011                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9012            ]);
 9013        });
 9014    });
 9015    editor.update(cx, |editor, cx| {
 9016        assert_text_with_selections(
 9017            editor,
 9018            indoc! {r#"
 9019                let a = {
 9020                    key:ˇ "value",
 9021                };
 9022            "#},
 9023            cx,
 9024        );
 9025    });
 9026    editor.update_in(cx, |editor, window, cx| {
 9027        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9028    });
 9029    editor.update(cx, |editor, cx| {
 9030        assert_text_with_selections(
 9031            editor,
 9032            indoc! {r#"
 9033                let a = {
 9034                    «ˇkey: "value"»,
 9035                };
 9036            "#},
 9037            cx,
 9038        );
 9039    });
 9040    editor.update_in(cx, |editor, window, cx| {
 9041        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9042    });
 9043    editor.update(cx, |editor, cx| {
 9044        assert_text_with_selections(
 9045            editor,
 9046            indoc! {r#"
 9047                let a = «ˇ{
 9048                    key: "value",
 9049                }»;
 9050            "#},
 9051            cx,
 9052        );
 9053    });
 9054
 9055    // Test case 3: Cursor after ','
 9056    editor.update_in(cx, |editor, window, cx| {
 9057        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9058            s.select_display_ranges([
 9059                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9060            ]);
 9061        });
 9062    });
 9063    editor.update(cx, |editor, cx| {
 9064        assert_text_with_selections(
 9065            editor,
 9066            indoc! {r#"
 9067                let a = {
 9068                    key: "value",ˇ
 9069                };
 9070            "#},
 9071            cx,
 9072        );
 9073    });
 9074    editor.update_in(cx, |editor, window, cx| {
 9075        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9076    });
 9077    editor.update(cx, |editor, cx| {
 9078        assert_text_with_selections(
 9079            editor,
 9080            indoc! {r#"
 9081                let a = «ˇ{
 9082                    key: "value",
 9083                }»;
 9084            "#},
 9085            cx,
 9086        );
 9087    });
 9088
 9089    // Test case 4: Cursor after ';'
 9090    editor.update_in(cx, |editor, window, cx| {
 9091        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9092            s.select_display_ranges([
 9093                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9094            ]);
 9095        });
 9096    });
 9097    editor.update(cx, |editor, cx| {
 9098        assert_text_with_selections(
 9099            editor,
 9100            indoc! {r#"
 9101                let a = {
 9102                    key: "value",
 9103                };ˇ
 9104            "#},
 9105            cx,
 9106        );
 9107    });
 9108    editor.update_in(cx, |editor, window, cx| {
 9109        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9110    });
 9111    editor.update(cx, |editor, cx| {
 9112        assert_text_with_selections(
 9113            editor,
 9114            indoc! {r#"
 9115                «ˇlet a = {
 9116                    key: "value",
 9117                };
 9118                »"#},
 9119            cx,
 9120        );
 9121    });
 9122}
 9123
 9124#[gpui::test]
 9125async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9126    init_test(cx, |_| {});
 9127
 9128    let language = Arc::new(Language::new(
 9129        LanguageConfig::default(),
 9130        Some(tree_sitter_rust::LANGUAGE.into()),
 9131    ));
 9132
 9133    let text = r#"
 9134        use mod1::mod2::{mod3, mod4};
 9135
 9136        fn fn_1(param1: bool, param2: &str) {
 9137            let var1 = "hello world";
 9138        }
 9139    "#
 9140    .unindent();
 9141
 9142    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9143    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9144    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9145
 9146    editor
 9147        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9148        .await;
 9149
 9150    // Test 1: Cursor on a letter of a string word
 9151    editor.update_in(cx, |editor, window, cx| {
 9152        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9153            s.select_display_ranges([
 9154                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9155            ]);
 9156        });
 9157    });
 9158    editor.update_in(cx, |editor, window, cx| {
 9159        assert_text_with_selections(
 9160            editor,
 9161            indoc! {r#"
 9162                use mod1::mod2::{mod3, mod4};
 9163
 9164                fn fn_1(param1: bool, param2: &str) {
 9165                    let var1 = "hˇello world";
 9166                }
 9167            "#},
 9168            cx,
 9169        );
 9170        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9171        assert_text_with_selections(
 9172            editor,
 9173            indoc! {r#"
 9174                use mod1::mod2::{mod3, mod4};
 9175
 9176                fn fn_1(param1: bool, param2: &str) {
 9177                    let var1 = "«ˇhello» world";
 9178                }
 9179            "#},
 9180            cx,
 9181        );
 9182    });
 9183
 9184    // Test 2: Partial selection within a word
 9185    editor.update_in(cx, |editor, window, cx| {
 9186        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9187            s.select_display_ranges([
 9188                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9189            ]);
 9190        });
 9191    });
 9192    editor.update_in(cx, |editor, window, cx| {
 9193        assert_text_with_selections(
 9194            editor,
 9195            indoc! {r#"
 9196                use mod1::mod2::{mod3, mod4};
 9197
 9198                fn fn_1(param1: bool, param2: &str) {
 9199                    let var1 = "h«elˇ»lo world";
 9200                }
 9201            "#},
 9202            cx,
 9203        );
 9204        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9205        assert_text_with_selections(
 9206            editor,
 9207            indoc! {r#"
 9208                use mod1::mod2::{mod3, mod4};
 9209
 9210                fn fn_1(param1: bool, param2: &str) {
 9211                    let var1 = "«ˇhello» world";
 9212                }
 9213            "#},
 9214            cx,
 9215        );
 9216    });
 9217
 9218    // Test 3: Complete word already selected
 9219    editor.update_in(cx, |editor, window, cx| {
 9220        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9221            s.select_display_ranges([
 9222                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9223            ]);
 9224        });
 9225    });
 9226    editor.update_in(cx, |editor, window, cx| {
 9227        assert_text_with_selections(
 9228            editor,
 9229            indoc! {r#"
 9230                use mod1::mod2::{mod3, mod4};
 9231
 9232                fn fn_1(param1: bool, param2: &str) {
 9233                    let var1 = "«helloˇ» world";
 9234                }
 9235            "#},
 9236            cx,
 9237        );
 9238        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9239        assert_text_with_selections(
 9240            editor,
 9241            indoc! {r#"
 9242                use mod1::mod2::{mod3, mod4};
 9243
 9244                fn fn_1(param1: bool, param2: &str) {
 9245                    let var1 = "«hello worldˇ»";
 9246                }
 9247            "#},
 9248            cx,
 9249        );
 9250    });
 9251
 9252    // Test 4: Selection spanning across words
 9253    editor.update_in(cx, |editor, window, cx| {
 9254        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9255            s.select_display_ranges([
 9256                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9257            ]);
 9258        });
 9259    });
 9260    editor.update_in(cx, |editor, window, cx| {
 9261        assert_text_with_selections(
 9262            editor,
 9263            indoc! {r#"
 9264                use mod1::mod2::{mod3, mod4};
 9265
 9266                fn fn_1(param1: bool, param2: &str) {
 9267                    let var1 = "hel«lo woˇ»rld";
 9268                }
 9269            "#},
 9270            cx,
 9271        );
 9272        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9273        assert_text_with_selections(
 9274            editor,
 9275            indoc! {r#"
 9276                use mod1::mod2::{mod3, mod4};
 9277
 9278                fn fn_1(param1: bool, param2: &str) {
 9279                    let var1 = "«ˇhello world»";
 9280                }
 9281            "#},
 9282            cx,
 9283        );
 9284    });
 9285
 9286    // Test 5: Expansion beyond string
 9287    editor.update_in(cx, |editor, window, cx| {
 9288        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9289        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9290        assert_text_with_selections(
 9291            editor,
 9292            indoc! {r#"
 9293                use mod1::mod2::{mod3, mod4};
 9294
 9295                fn fn_1(param1: bool, param2: &str) {
 9296                    «ˇlet var1 = "hello world";»
 9297                }
 9298            "#},
 9299            cx,
 9300        );
 9301    });
 9302}
 9303
 9304#[gpui::test]
 9305async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9306    init_test(cx, |_| {});
 9307
 9308    let mut cx = EditorTestContext::new(cx).await;
 9309
 9310    let language = Arc::new(Language::new(
 9311        LanguageConfig::default(),
 9312        Some(tree_sitter_rust::LANGUAGE.into()),
 9313    ));
 9314
 9315    cx.update_buffer(|buffer, cx| {
 9316        buffer.set_language(Some(language), cx);
 9317    });
 9318
 9319    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9320    cx.update_editor(|editor, window, cx| {
 9321        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9322    });
 9323
 9324    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9325
 9326    cx.set_state(indoc! { r#"fn a() {
 9327          // what
 9328          // a
 9329          // ˇlong
 9330          // method
 9331          // I
 9332          // sure
 9333          // hope
 9334          // it
 9335          // works
 9336    }"# });
 9337
 9338    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9339    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9340    cx.update(|_, cx| {
 9341        multi_buffer.update(cx, |multi_buffer, cx| {
 9342            multi_buffer.set_excerpts_for_path(
 9343                PathKey::for_buffer(&buffer, cx),
 9344                buffer,
 9345                [Point::new(1, 0)..Point::new(1, 0)],
 9346                3,
 9347                cx,
 9348            );
 9349        });
 9350    });
 9351
 9352    let editor2 = cx.new_window_entity(|window, cx| {
 9353        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9354    });
 9355
 9356    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9357    cx.update_editor(|editor, window, cx| {
 9358        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9359            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9360        })
 9361    });
 9362
 9363    cx.assert_editor_state(indoc! { "
 9364        fn a() {
 9365              // what
 9366              // a
 9367        ˇ      // long
 9368              // method"});
 9369
 9370    cx.update_editor(|editor, window, cx| {
 9371        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9372    });
 9373
 9374    // Although we could potentially make the action work when the syntax node
 9375    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9376    // did. Maybe we could also expand the excerpt to contain the range?
 9377    cx.assert_editor_state(indoc! { "
 9378        fn a() {
 9379              // what
 9380              // a
 9381        ˇ      // long
 9382              // method"});
 9383}
 9384
 9385#[gpui::test]
 9386async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9387    init_test(cx, |_| {});
 9388
 9389    let base_text = r#"
 9390        impl A {
 9391            // this is an uncommitted comment
 9392
 9393            fn b() {
 9394                c();
 9395            }
 9396
 9397            // this is another uncommitted comment
 9398
 9399            fn d() {
 9400                // e
 9401                // f
 9402            }
 9403        }
 9404
 9405        fn g() {
 9406            // h
 9407        }
 9408    "#
 9409    .unindent();
 9410
 9411    let text = r#"
 9412        ˇimpl A {
 9413
 9414            fn b() {
 9415                c();
 9416            }
 9417
 9418            fn d() {
 9419                // e
 9420                // f
 9421            }
 9422        }
 9423
 9424        fn g() {
 9425            // h
 9426        }
 9427    "#
 9428    .unindent();
 9429
 9430    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9431    cx.set_state(&text);
 9432    cx.set_head_text(&base_text);
 9433    cx.update_editor(|editor, window, cx| {
 9434        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9435    });
 9436
 9437    cx.assert_state_with_diff(
 9438        "
 9439        ˇimpl A {
 9440      -     // this is an uncommitted comment
 9441
 9442            fn b() {
 9443                c();
 9444            }
 9445
 9446      -     // this is another uncommitted comment
 9447      -
 9448            fn d() {
 9449                // e
 9450                // f
 9451            }
 9452        }
 9453
 9454        fn g() {
 9455            // h
 9456        }
 9457    "
 9458        .unindent(),
 9459    );
 9460
 9461    let expected_display_text = "
 9462        impl A {
 9463            // this is an uncommitted comment
 9464
 9465            fn b() {
 9466 9467            }
 9468
 9469            // this is another uncommitted comment
 9470
 9471            fn d() {
 9472 9473            }
 9474        }
 9475
 9476        fn g() {
 9477 9478        }
 9479        "
 9480    .unindent();
 9481
 9482    cx.update_editor(|editor, window, cx| {
 9483        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9484        assert_eq!(editor.display_text(cx), expected_display_text);
 9485    });
 9486}
 9487
 9488#[gpui::test]
 9489async fn test_autoindent(cx: &mut TestAppContext) {
 9490    init_test(cx, |_| {});
 9491
 9492    let language = Arc::new(
 9493        Language::new(
 9494            LanguageConfig {
 9495                brackets: BracketPairConfig {
 9496                    pairs: vec![
 9497                        BracketPair {
 9498                            start: "{".to_string(),
 9499                            end: "}".to_string(),
 9500                            close: false,
 9501                            surround: false,
 9502                            newline: true,
 9503                        },
 9504                        BracketPair {
 9505                            start: "(".to_string(),
 9506                            end: ")".to_string(),
 9507                            close: false,
 9508                            surround: false,
 9509                            newline: true,
 9510                        },
 9511                    ],
 9512                    ..Default::default()
 9513                },
 9514                ..Default::default()
 9515            },
 9516            Some(tree_sitter_rust::LANGUAGE.into()),
 9517        )
 9518        .with_indents_query(
 9519            r#"
 9520                (_ "(" ")" @end) @indent
 9521                (_ "{" "}" @end) @indent
 9522            "#,
 9523        )
 9524        .unwrap(),
 9525    );
 9526
 9527    let text = "fn a() {}";
 9528
 9529    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9530    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9531    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9532    editor
 9533        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9534        .await;
 9535
 9536    editor.update_in(cx, |editor, window, cx| {
 9537        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9538            s.select_ranges([5..5, 8..8, 9..9])
 9539        });
 9540        editor.newline(&Newline, window, cx);
 9541        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9542        assert_eq!(
 9543            editor.selections.ranges(cx),
 9544            &[
 9545                Point::new(1, 4)..Point::new(1, 4),
 9546                Point::new(3, 4)..Point::new(3, 4),
 9547                Point::new(5, 0)..Point::new(5, 0)
 9548            ]
 9549        );
 9550    });
 9551}
 9552
 9553#[gpui::test]
 9554async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9555    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9556
 9557    let language = Arc::new(
 9558        Language::new(
 9559            LanguageConfig {
 9560                brackets: BracketPairConfig {
 9561                    pairs: vec![
 9562                        BracketPair {
 9563                            start: "{".to_string(),
 9564                            end: "}".to_string(),
 9565                            close: false,
 9566                            surround: false,
 9567                            newline: true,
 9568                        },
 9569                        BracketPair {
 9570                            start: "(".to_string(),
 9571                            end: ")".to_string(),
 9572                            close: false,
 9573                            surround: false,
 9574                            newline: true,
 9575                        },
 9576                    ],
 9577                    ..Default::default()
 9578                },
 9579                ..Default::default()
 9580            },
 9581            Some(tree_sitter_rust::LANGUAGE.into()),
 9582        )
 9583        .with_indents_query(
 9584            r#"
 9585                (_ "(" ")" @end) @indent
 9586                (_ "{" "}" @end) @indent
 9587            "#,
 9588        )
 9589        .unwrap(),
 9590    );
 9591
 9592    let text = "fn a() {}";
 9593
 9594    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9595    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9596    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9597    editor
 9598        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9599        .await;
 9600
 9601    editor.update_in(cx, |editor, window, cx| {
 9602        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9603            s.select_ranges([5..5, 8..8, 9..9])
 9604        });
 9605        editor.newline(&Newline, window, cx);
 9606        assert_eq!(
 9607            editor.text(cx),
 9608            indoc!(
 9609                "
 9610                fn a(
 9611
 9612                ) {
 9613
 9614                }
 9615                "
 9616            )
 9617        );
 9618        assert_eq!(
 9619            editor.selections.ranges(cx),
 9620            &[
 9621                Point::new(1, 0)..Point::new(1, 0),
 9622                Point::new(3, 0)..Point::new(3, 0),
 9623                Point::new(5, 0)..Point::new(5, 0)
 9624            ]
 9625        );
 9626    });
 9627}
 9628
 9629#[gpui::test]
 9630async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9631    init_test(cx, |settings| {
 9632        settings.defaults.auto_indent = Some(true);
 9633        settings.languages.0.insert(
 9634            "python".into(),
 9635            LanguageSettingsContent {
 9636                auto_indent: Some(false),
 9637                ..Default::default()
 9638            },
 9639        );
 9640    });
 9641
 9642    let mut cx = EditorTestContext::new(cx).await;
 9643
 9644    let injected_language = Arc::new(
 9645        Language::new(
 9646            LanguageConfig {
 9647                brackets: BracketPairConfig {
 9648                    pairs: vec![
 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: false,
 9661                            newline: true,
 9662                        },
 9663                    ],
 9664                    ..Default::default()
 9665                },
 9666                name: "python".into(),
 9667                ..Default::default()
 9668            },
 9669            Some(tree_sitter_python::LANGUAGE.into()),
 9670        )
 9671        .with_indents_query(
 9672            r#"
 9673                (_ "(" ")" @end) @indent
 9674                (_ "{" "}" @end) @indent
 9675            "#,
 9676        )
 9677        .unwrap(),
 9678    );
 9679
 9680    let language = Arc::new(
 9681        Language::new(
 9682            LanguageConfig {
 9683                brackets: BracketPairConfig {
 9684                    pairs: vec![
 9685                        BracketPair {
 9686                            start: "{".to_string(),
 9687                            end: "}".to_string(),
 9688                            close: false,
 9689                            surround: false,
 9690                            newline: true,
 9691                        },
 9692                        BracketPair {
 9693                            start: "(".to_string(),
 9694                            end: ")".to_string(),
 9695                            close: true,
 9696                            surround: false,
 9697                            newline: true,
 9698                        },
 9699                    ],
 9700                    ..Default::default()
 9701                },
 9702                name: LanguageName::new("rust"),
 9703                ..Default::default()
 9704            },
 9705            Some(tree_sitter_rust::LANGUAGE.into()),
 9706        )
 9707        .with_indents_query(
 9708            r#"
 9709                (_ "(" ")" @end) @indent
 9710                (_ "{" "}" @end) @indent
 9711            "#,
 9712        )
 9713        .unwrap()
 9714        .with_injection_query(
 9715            r#"
 9716            (macro_invocation
 9717                macro: (identifier) @_macro_name
 9718                (token_tree) @injection.content
 9719                (#set! injection.language "python"))
 9720           "#,
 9721        )
 9722        .unwrap(),
 9723    );
 9724
 9725    cx.language_registry().add(injected_language);
 9726    cx.language_registry().add(language.clone());
 9727
 9728    cx.update_buffer(|buffer, cx| {
 9729        buffer.set_language(Some(language), cx);
 9730    });
 9731
 9732    cx.set_state(r#"struct A {ˇ}"#);
 9733
 9734    cx.update_editor(|editor, window, cx| {
 9735        editor.newline(&Default::default(), window, cx);
 9736    });
 9737
 9738    cx.assert_editor_state(indoc!(
 9739        "struct A {
 9740            ˇ
 9741        }"
 9742    ));
 9743
 9744    cx.set_state(r#"select_biased!(ˇ)"#);
 9745
 9746    cx.update_editor(|editor, window, cx| {
 9747        editor.newline(&Default::default(), window, cx);
 9748        editor.handle_input("def ", window, cx);
 9749        editor.handle_input("(", window, cx);
 9750        editor.newline(&Default::default(), window, cx);
 9751        editor.handle_input("a", window, cx);
 9752    });
 9753
 9754    cx.assert_editor_state(indoc!(
 9755        "select_biased!(
 9756        def (
 9757 9758        )
 9759        )"
 9760    ));
 9761}
 9762
 9763#[gpui::test]
 9764async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9765    init_test(cx, |_| {});
 9766
 9767    {
 9768        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9769        cx.set_state(indoc! {"
 9770            impl A {
 9771
 9772                fn b() {}
 9773
 9774            «fn c() {
 9775
 9776            }ˇ»
 9777            }
 9778        "});
 9779
 9780        cx.update_editor(|editor, window, cx| {
 9781            editor.autoindent(&Default::default(), window, cx);
 9782        });
 9783
 9784        cx.assert_editor_state(indoc! {"
 9785            impl A {
 9786
 9787                fn b() {}
 9788
 9789                «fn c() {
 9790
 9791                }ˇ»
 9792            }
 9793        "});
 9794    }
 9795
 9796    {
 9797        let mut cx = EditorTestContext::new_multibuffer(
 9798            cx,
 9799            [indoc! { "
 9800                impl A {
 9801                «
 9802                // a
 9803                fn b(){}
 9804                »
 9805                «
 9806                    }
 9807                    fn c(){}
 9808                »
 9809            "}],
 9810        );
 9811
 9812        let buffer = cx.update_editor(|editor, _, cx| {
 9813            let buffer = editor.buffer().update(cx, |buffer, _| {
 9814                buffer.all_buffers().iter().next().unwrap().clone()
 9815            });
 9816            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9817            buffer
 9818        });
 9819
 9820        cx.run_until_parked();
 9821        cx.update_editor(|editor, window, cx| {
 9822            editor.select_all(&Default::default(), window, cx);
 9823            editor.autoindent(&Default::default(), window, cx)
 9824        });
 9825        cx.run_until_parked();
 9826
 9827        cx.update(|_, cx| {
 9828            assert_eq!(
 9829                buffer.read(cx).text(),
 9830                indoc! { "
 9831                    impl A {
 9832
 9833                        // a
 9834                        fn b(){}
 9835
 9836
 9837                    }
 9838                    fn c(){}
 9839
 9840                " }
 9841            )
 9842        });
 9843    }
 9844}
 9845
 9846#[gpui::test]
 9847async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9848    init_test(cx, |_| {});
 9849
 9850    let mut cx = EditorTestContext::new(cx).await;
 9851
 9852    let language = Arc::new(Language::new(
 9853        LanguageConfig {
 9854            brackets: BracketPairConfig {
 9855                pairs: vec![
 9856                    BracketPair {
 9857                        start: "{".to_string(),
 9858                        end: "}".to_string(),
 9859                        close: true,
 9860                        surround: true,
 9861                        newline: true,
 9862                    },
 9863                    BracketPair {
 9864                        start: "(".to_string(),
 9865                        end: ")".to_string(),
 9866                        close: true,
 9867                        surround: true,
 9868                        newline: true,
 9869                    },
 9870                    BracketPair {
 9871                        start: "/*".to_string(),
 9872                        end: " */".to_string(),
 9873                        close: true,
 9874                        surround: true,
 9875                        newline: true,
 9876                    },
 9877                    BracketPair {
 9878                        start: "[".to_string(),
 9879                        end: "]".to_string(),
 9880                        close: false,
 9881                        surround: false,
 9882                        newline: true,
 9883                    },
 9884                    BracketPair {
 9885                        start: "\"".to_string(),
 9886                        end: "\"".to_string(),
 9887                        close: true,
 9888                        surround: true,
 9889                        newline: false,
 9890                    },
 9891                    BracketPair {
 9892                        start: "<".to_string(),
 9893                        end: ">".to_string(),
 9894                        close: false,
 9895                        surround: true,
 9896                        newline: true,
 9897                    },
 9898                ],
 9899                ..Default::default()
 9900            },
 9901            autoclose_before: "})]".to_string(),
 9902            ..Default::default()
 9903        },
 9904        Some(tree_sitter_rust::LANGUAGE.into()),
 9905    ));
 9906
 9907    cx.language_registry().add(language.clone());
 9908    cx.update_buffer(|buffer, cx| {
 9909        buffer.set_language(Some(language), cx);
 9910    });
 9911
 9912    cx.set_state(
 9913        &r#"
 9914            🏀ˇ
 9915            εˇ
 9916            ❤️ˇ
 9917        "#
 9918        .unindent(),
 9919    );
 9920
 9921    // autoclose multiple nested brackets at multiple cursors
 9922    cx.update_editor(|editor, window, cx| {
 9923        editor.handle_input("{", window, cx);
 9924        editor.handle_input("{", window, cx);
 9925        editor.handle_input("{", window, cx);
 9926    });
 9927    cx.assert_editor_state(
 9928        &"
 9929            🏀{{{ˇ}}}
 9930            ε{{{ˇ}}}
 9931            ❤️{{{ˇ}}}
 9932        "
 9933        .unindent(),
 9934    );
 9935
 9936    // insert a different closing bracket
 9937    cx.update_editor(|editor, window, cx| {
 9938        editor.handle_input(")", window, cx);
 9939    });
 9940    cx.assert_editor_state(
 9941        &"
 9942            🏀{{{)ˇ}}}
 9943            ε{{{)ˇ}}}
 9944            ❤️{{{)ˇ}}}
 9945        "
 9946        .unindent(),
 9947    );
 9948
 9949    // skip over the auto-closed brackets when typing a closing bracket
 9950    cx.update_editor(|editor, window, cx| {
 9951        editor.move_right(&MoveRight, window, cx);
 9952        editor.handle_input("}", window, cx);
 9953        editor.handle_input("}", window, cx);
 9954        editor.handle_input("}", window, cx);
 9955    });
 9956    cx.assert_editor_state(
 9957        &"
 9958            🏀{{{)}}}}ˇ
 9959            ε{{{)}}}}ˇ
 9960            ❤️{{{)}}}}ˇ
 9961        "
 9962        .unindent(),
 9963    );
 9964
 9965    // autoclose multi-character pairs
 9966    cx.set_state(
 9967        &"
 9968            ˇ
 9969            ˇ
 9970        "
 9971        .unindent(),
 9972    );
 9973    cx.update_editor(|editor, window, cx| {
 9974        editor.handle_input("/", window, cx);
 9975        editor.handle_input("*", window, cx);
 9976    });
 9977    cx.assert_editor_state(
 9978        &"
 9979            /*ˇ */
 9980            /*ˇ */
 9981        "
 9982        .unindent(),
 9983    );
 9984
 9985    // one cursor autocloses a multi-character pair, one cursor
 9986    // does not autoclose.
 9987    cx.set_state(
 9988        &"
 9989 9990            ˇ
 9991        "
 9992        .unindent(),
 9993    );
 9994    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 9995    cx.assert_editor_state(
 9996        &"
 9997            /*ˇ */
 9998 9999        "
10000        .unindent(),
10001    );
10002
10003    // Don't autoclose if the next character isn't whitespace and isn't
10004    // listed in the language's "autoclose_before" section.
10005    cx.set_state("ˇa b");
10006    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10007    cx.assert_editor_state("{ˇa b");
10008
10009    // Don't autoclose if `close` is false for the bracket pair
10010    cx.set_state("ˇ");
10011    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10012    cx.assert_editor_state("");
10013
10014    // Surround with brackets if text is selected
10015    cx.set_state("«aˇ» b");
10016    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10017    cx.assert_editor_state("{«aˇ»} b");
10018
10019    // Autoclose when not immediately after a word character
10020    cx.set_state("a ˇ");
10021    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10022    cx.assert_editor_state("a \"ˇ\"");
10023
10024    // Autoclose pair where the start and end characters are the same
10025    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10026    cx.assert_editor_state("a \"\"ˇ");
10027
10028    // Don't autoclose when immediately after a word character
10029    cx.set_state("");
10030    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10031    cx.assert_editor_state("a\"ˇ");
10032
10033    // Do autoclose when after a non-word character
10034    cx.set_state("");
10035    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10036    cx.assert_editor_state("{\"ˇ\"");
10037
10038    // Non identical pairs autoclose regardless of preceding character
10039    cx.set_state("");
10040    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10041    cx.assert_editor_state("a{ˇ}");
10042
10043    // Don't autoclose pair if autoclose is disabled
10044    cx.set_state("ˇ");
10045    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10046    cx.assert_editor_state("");
10047
10048    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10049    cx.set_state("«aˇ» b");
10050    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10051    cx.assert_editor_state("<«aˇ»> b");
10052}
10053
10054#[gpui::test]
10055async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10056    init_test(cx, |settings| {
10057        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10058    });
10059
10060    let mut cx = EditorTestContext::new(cx).await;
10061
10062    let language = Arc::new(Language::new(
10063        LanguageConfig {
10064            brackets: BracketPairConfig {
10065                pairs: vec![
10066                    BracketPair {
10067                        start: "{".to_string(),
10068                        end: "}".to_string(),
10069                        close: true,
10070                        surround: true,
10071                        newline: true,
10072                    },
10073                    BracketPair {
10074                        start: "(".to_string(),
10075                        end: ")".to_string(),
10076                        close: true,
10077                        surround: true,
10078                        newline: true,
10079                    },
10080                    BracketPair {
10081                        start: "[".to_string(),
10082                        end: "]".to_string(),
10083                        close: false,
10084                        surround: false,
10085                        newline: true,
10086                    },
10087                ],
10088                ..Default::default()
10089            },
10090            autoclose_before: "})]".to_string(),
10091            ..Default::default()
10092        },
10093        Some(tree_sitter_rust::LANGUAGE.into()),
10094    ));
10095
10096    cx.language_registry().add(language.clone());
10097    cx.update_buffer(|buffer, cx| {
10098        buffer.set_language(Some(language), cx);
10099    });
10100
10101    cx.set_state(
10102        &"
10103            ˇ
10104            ˇ
10105            ˇ
10106        "
10107        .unindent(),
10108    );
10109
10110    // ensure only matching closing brackets are skipped over
10111    cx.update_editor(|editor, window, cx| {
10112        editor.handle_input("}", window, cx);
10113        editor.move_left(&MoveLeft, window, cx);
10114        editor.handle_input(")", window, cx);
10115        editor.move_left(&MoveLeft, window, cx);
10116    });
10117    cx.assert_editor_state(
10118        &"
10119            ˇ)}
10120            ˇ)}
10121            ˇ)}
10122        "
10123        .unindent(),
10124    );
10125
10126    // skip-over closing brackets at multiple cursors
10127    cx.update_editor(|editor, window, cx| {
10128        editor.handle_input(")", window, cx);
10129        editor.handle_input("}", window, cx);
10130    });
10131    cx.assert_editor_state(
10132        &"
10133            )}ˇ
10134            )}ˇ
10135            )}ˇ
10136        "
10137        .unindent(),
10138    );
10139
10140    // ignore non-close brackets
10141    cx.update_editor(|editor, window, cx| {
10142        editor.handle_input("]", window, cx);
10143        editor.move_left(&MoveLeft, window, cx);
10144        editor.handle_input("]", window, cx);
10145    });
10146    cx.assert_editor_state(
10147        &"
10148            )}]ˇ]
10149            )}]ˇ]
10150            )}]ˇ]
10151        "
10152        .unindent(),
10153    );
10154}
10155
10156#[gpui::test]
10157async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10158    init_test(cx, |_| {});
10159
10160    let mut cx = EditorTestContext::new(cx).await;
10161
10162    let html_language = Arc::new(
10163        Language::new(
10164            LanguageConfig {
10165                name: "HTML".into(),
10166                brackets: BracketPairConfig {
10167                    pairs: vec![
10168                        BracketPair {
10169                            start: "<".into(),
10170                            end: ">".into(),
10171                            close: true,
10172                            ..Default::default()
10173                        },
10174                        BracketPair {
10175                            start: "{".into(),
10176                            end: "}".into(),
10177                            close: true,
10178                            ..Default::default()
10179                        },
10180                        BracketPair {
10181                            start: "(".into(),
10182                            end: ")".into(),
10183                            close: true,
10184                            ..Default::default()
10185                        },
10186                    ],
10187                    ..Default::default()
10188                },
10189                autoclose_before: "})]>".into(),
10190                ..Default::default()
10191            },
10192            Some(tree_sitter_html::LANGUAGE.into()),
10193        )
10194        .with_injection_query(
10195            r#"
10196            (script_element
10197                (raw_text) @injection.content
10198                (#set! injection.language "javascript"))
10199            "#,
10200        )
10201        .unwrap(),
10202    );
10203
10204    let javascript_language = Arc::new(Language::new(
10205        LanguageConfig {
10206            name: "JavaScript".into(),
10207            brackets: BracketPairConfig {
10208                pairs: vec![
10209                    BracketPair {
10210                        start: "/*".into(),
10211                        end: " */".into(),
10212                        close: true,
10213                        ..Default::default()
10214                    },
10215                    BracketPair {
10216                        start: "{".into(),
10217                        end: "}".into(),
10218                        close: true,
10219                        ..Default::default()
10220                    },
10221                    BracketPair {
10222                        start: "(".into(),
10223                        end: ")".into(),
10224                        close: true,
10225                        ..Default::default()
10226                    },
10227                ],
10228                ..Default::default()
10229            },
10230            autoclose_before: "})]>".into(),
10231            ..Default::default()
10232        },
10233        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10234    ));
10235
10236    cx.language_registry().add(html_language.clone());
10237    cx.language_registry().add(javascript_language);
10238    cx.executor().run_until_parked();
10239
10240    cx.update_buffer(|buffer, cx| {
10241        buffer.set_language(Some(html_language), cx);
10242    });
10243
10244    cx.set_state(
10245        &r#"
10246            <body>ˇ
10247                <script>
10248                    var x = 1;ˇ
10249                </script>
10250            </body>ˇ
10251        "#
10252        .unindent(),
10253    );
10254
10255    // Precondition: different languages are active at different locations.
10256    cx.update_editor(|editor, window, cx| {
10257        let snapshot = editor.snapshot(window, cx);
10258        let cursors = editor.selections.ranges::<usize>(cx);
10259        let languages = cursors
10260            .iter()
10261            .map(|c| snapshot.language_at(c.start).unwrap().name())
10262            .collect::<Vec<_>>();
10263        assert_eq!(
10264            languages,
10265            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10266        );
10267    });
10268
10269    // Angle brackets autoclose in HTML, but not JavaScript.
10270    cx.update_editor(|editor, window, cx| {
10271        editor.handle_input("<", window, cx);
10272        editor.handle_input("a", window, cx);
10273    });
10274    cx.assert_editor_state(
10275        &r#"
10276            <body><aˇ>
10277                <script>
10278                    var x = 1;<aˇ
10279                </script>
10280            </body><aˇ>
10281        "#
10282        .unindent(),
10283    );
10284
10285    // Curly braces and parens autoclose in both HTML and JavaScript.
10286    cx.update_editor(|editor, window, cx| {
10287        editor.handle_input(" b=", window, cx);
10288        editor.handle_input("{", window, cx);
10289        editor.handle_input("c", window, cx);
10290        editor.handle_input("(", window, cx);
10291    });
10292    cx.assert_editor_state(
10293        &r#"
10294            <body><a b={c(ˇ)}>
10295                <script>
10296                    var x = 1;<a b={c(ˇ)}
10297                </script>
10298            </body><a b={c(ˇ)}>
10299        "#
10300        .unindent(),
10301    );
10302
10303    // Brackets that were already autoclosed are skipped.
10304    cx.update_editor(|editor, window, cx| {
10305        editor.handle_input(")", window, cx);
10306        editor.handle_input("d", window, cx);
10307        editor.handle_input("}", window, cx);
10308    });
10309    cx.assert_editor_state(
10310        &r#"
10311            <body><a b={c()d}ˇ>
10312                <script>
10313                    var x = 1;<a b={c()d}ˇ
10314                </script>
10315            </body><a b={c()d}ˇ>
10316        "#
10317        .unindent(),
10318    );
10319    cx.update_editor(|editor, window, cx| {
10320        editor.handle_input(">", window, cx);
10321    });
10322    cx.assert_editor_state(
10323        &r#"
10324            <body><a b={c()d}>ˇ
10325                <script>
10326                    var x = 1;<a b={c()d}>ˇ
10327                </script>
10328            </body><a b={c()d}>ˇ
10329        "#
10330        .unindent(),
10331    );
10332
10333    // Reset
10334    cx.set_state(
10335        &r#"
10336            <body>ˇ
10337                <script>
10338                    var x = 1;ˇ
10339                </script>
10340            </body>ˇ
10341        "#
10342        .unindent(),
10343    );
10344
10345    cx.update_editor(|editor, window, cx| {
10346        editor.handle_input("<", window, cx);
10347    });
10348    cx.assert_editor_state(
10349        &r#"
10350            <body><ˇ>
10351                <script>
10352                    var x = 1;<ˇ
10353                </script>
10354            </body><ˇ>
10355        "#
10356        .unindent(),
10357    );
10358
10359    // When backspacing, the closing angle brackets are removed.
10360    cx.update_editor(|editor, window, cx| {
10361        editor.backspace(&Backspace, window, cx);
10362    });
10363    cx.assert_editor_state(
10364        &r#"
10365            <body>ˇ
10366                <script>
10367                    var x = 1;ˇ
10368                </script>
10369            </body>ˇ
10370        "#
10371        .unindent(),
10372    );
10373
10374    // Block comments autoclose in JavaScript, but not HTML.
10375    cx.update_editor(|editor, window, cx| {
10376        editor.handle_input("/", window, cx);
10377        editor.handle_input("*", window, cx);
10378    });
10379    cx.assert_editor_state(
10380        &r#"
10381            <body>/*ˇ
10382                <script>
10383                    var x = 1;/*ˇ */
10384                </script>
10385            </body>/*ˇ
10386        "#
10387        .unindent(),
10388    );
10389}
10390
10391#[gpui::test]
10392async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10393    init_test(cx, |_| {});
10394
10395    let mut cx = EditorTestContext::new(cx).await;
10396
10397    let rust_language = Arc::new(
10398        Language::new(
10399            LanguageConfig {
10400                name: "Rust".into(),
10401                brackets: serde_json::from_value(json!([
10402                    { "start": "{", "end": "}", "close": true, "newline": true },
10403                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10404                ]))
10405                .unwrap(),
10406                autoclose_before: "})]>".into(),
10407                ..Default::default()
10408            },
10409            Some(tree_sitter_rust::LANGUAGE.into()),
10410        )
10411        .with_override_query("(string_literal) @string")
10412        .unwrap(),
10413    );
10414
10415    cx.language_registry().add(rust_language.clone());
10416    cx.update_buffer(|buffer, cx| {
10417        buffer.set_language(Some(rust_language), cx);
10418    });
10419
10420    cx.set_state(
10421        &r#"
10422            let x = ˇ
10423        "#
10424        .unindent(),
10425    );
10426
10427    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10428    cx.update_editor(|editor, window, cx| {
10429        editor.handle_input("\"", window, cx);
10430    });
10431    cx.assert_editor_state(
10432        &r#"
10433            let x = "ˇ"
10434        "#
10435        .unindent(),
10436    );
10437
10438    // Inserting another quotation mark. The cursor moves across the existing
10439    // automatically-inserted quotation mark.
10440    cx.update_editor(|editor, window, cx| {
10441        editor.handle_input("\"", window, cx);
10442    });
10443    cx.assert_editor_state(
10444        &r#"
10445            let x = ""ˇ
10446        "#
10447        .unindent(),
10448    );
10449
10450    // Reset
10451    cx.set_state(
10452        &r#"
10453            let x = ˇ
10454        "#
10455        .unindent(),
10456    );
10457
10458    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10459    cx.update_editor(|editor, window, cx| {
10460        editor.handle_input("\"", window, cx);
10461        editor.handle_input(" ", window, cx);
10462        editor.move_left(&Default::default(), window, cx);
10463        editor.handle_input("\\", window, cx);
10464        editor.handle_input("\"", window, cx);
10465    });
10466    cx.assert_editor_state(
10467        &r#"
10468            let x = "\"ˇ "
10469        "#
10470        .unindent(),
10471    );
10472
10473    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10474    // mark. Nothing is inserted.
10475    cx.update_editor(|editor, window, cx| {
10476        editor.move_right(&Default::default(), window, cx);
10477        editor.handle_input("\"", window, cx);
10478    });
10479    cx.assert_editor_state(
10480        &r#"
10481            let x = "\" "ˇ
10482        "#
10483        .unindent(),
10484    );
10485}
10486
10487#[gpui::test]
10488async fn test_surround_with_pair(cx: &mut TestAppContext) {
10489    init_test(cx, |_| {});
10490
10491    let language = Arc::new(Language::new(
10492        LanguageConfig {
10493            brackets: BracketPairConfig {
10494                pairs: vec![
10495                    BracketPair {
10496                        start: "{".to_string(),
10497                        end: "}".to_string(),
10498                        close: true,
10499                        surround: true,
10500                        newline: true,
10501                    },
10502                    BracketPair {
10503                        start: "/* ".to_string(),
10504                        end: "*/".to_string(),
10505                        close: true,
10506                        surround: true,
10507                        ..Default::default()
10508                    },
10509                ],
10510                ..Default::default()
10511            },
10512            ..Default::default()
10513        },
10514        Some(tree_sitter_rust::LANGUAGE.into()),
10515    ));
10516
10517    let text = r#"
10518        a
10519        b
10520        c
10521    "#
10522    .unindent();
10523
10524    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10525    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10526    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10527    editor
10528        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10529        .await;
10530
10531    editor.update_in(cx, |editor, window, cx| {
10532        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10533            s.select_display_ranges([
10534                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10535                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10536                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10537            ])
10538        });
10539
10540        editor.handle_input("{", window, cx);
10541        editor.handle_input("{", window, cx);
10542        editor.handle_input("{", window, cx);
10543        assert_eq!(
10544            editor.text(cx),
10545            "
10546                {{{a}}}
10547                {{{b}}}
10548                {{{c}}}
10549            "
10550            .unindent()
10551        );
10552        assert_eq!(
10553            editor.selections.display_ranges(cx),
10554            [
10555                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10556                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10557                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10558            ]
10559        );
10560
10561        editor.undo(&Undo, window, cx);
10562        editor.undo(&Undo, window, cx);
10563        editor.undo(&Undo, window, cx);
10564        assert_eq!(
10565            editor.text(cx),
10566            "
10567                a
10568                b
10569                c
10570            "
10571            .unindent()
10572        );
10573        assert_eq!(
10574            editor.selections.display_ranges(cx),
10575            [
10576                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10577                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10578                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10579            ]
10580        );
10581
10582        // Ensure inserting the first character of a multi-byte bracket pair
10583        // doesn't surround the selections with the bracket.
10584        editor.handle_input("/", window, cx);
10585        assert_eq!(
10586            editor.text(cx),
10587            "
10588                /
10589                /
10590                /
10591            "
10592            .unindent()
10593        );
10594        assert_eq!(
10595            editor.selections.display_ranges(cx),
10596            [
10597                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10598                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10599                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10600            ]
10601        );
10602
10603        editor.undo(&Undo, window, cx);
10604        assert_eq!(
10605            editor.text(cx),
10606            "
10607                a
10608                b
10609                c
10610            "
10611            .unindent()
10612        );
10613        assert_eq!(
10614            editor.selections.display_ranges(cx),
10615            [
10616                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10617                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10618                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10619            ]
10620        );
10621
10622        // Ensure inserting the last character of a multi-byte bracket pair
10623        // doesn't surround the selections with the bracket.
10624        editor.handle_input("*", window, cx);
10625        assert_eq!(
10626            editor.text(cx),
10627            "
10628                *
10629                *
10630                *
10631            "
10632            .unindent()
10633        );
10634        assert_eq!(
10635            editor.selections.display_ranges(cx),
10636            [
10637                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10638                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10639                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10640            ]
10641        );
10642    });
10643}
10644
10645#[gpui::test]
10646async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10647    init_test(cx, |_| {});
10648
10649    let language = Arc::new(Language::new(
10650        LanguageConfig {
10651            brackets: BracketPairConfig {
10652                pairs: vec![BracketPair {
10653                    start: "{".to_string(),
10654                    end: "}".to_string(),
10655                    close: true,
10656                    surround: true,
10657                    newline: true,
10658                }],
10659                ..Default::default()
10660            },
10661            autoclose_before: "}".to_string(),
10662            ..Default::default()
10663        },
10664        Some(tree_sitter_rust::LANGUAGE.into()),
10665    ));
10666
10667    let text = r#"
10668        a
10669        b
10670        c
10671    "#
10672    .unindent();
10673
10674    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10675    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10676    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10677    editor
10678        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10679        .await;
10680
10681    editor.update_in(cx, |editor, window, cx| {
10682        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10683            s.select_ranges([
10684                Point::new(0, 1)..Point::new(0, 1),
10685                Point::new(1, 1)..Point::new(1, 1),
10686                Point::new(2, 1)..Point::new(2, 1),
10687            ])
10688        });
10689
10690        editor.handle_input("{", window, cx);
10691        editor.handle_input("{", window, cx);
10692        editor.handle_input("_", window, cx);
10693        assert_eq!(
10694            editor.text(cx),
10695            "
10696                a{{_}}
10697                b{{_}}
10698                c{{_}}
10699            "
10700            .unindent()
10701        );
10702        assert_eq!(
10703            editor.selections.ranges::<Point>(cx),
10704            [
10705                Point::new(0, 4)..Point::new(0, 4),
10706                Point::new(1, 4)..Point::new(1, 4),
10707                Point::new(2, 4)..Point::new(2, 4)
10708            ]
10709        );
10710
10711        editor.backspace(&Default::default(), window, cx);
10712        editor.backspace(&Default::default(), window, cx);
10713        assert_eq!(
10714            editor.text(cx),
10715            "
10716                a{}
10717                b{}
10718                c{}
10719            "
10720            .unindent()
10721        );
10722        assert_eq!(
10723            editor.selections.ranges::<Point>(cx),
10724            [
10725                Point::new(0, 2)..Point::new(0, 2),
10726                Point::new(1, 2)..Point::new(1, 2),
10727                Point::new(2, 2)..Point::new(2, 2)
10728            ]
10729        );
10730
10731        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10732        assert_eq!(
10733            editor.text(cx),
10734            "
10735                a
10736                b
10737                c
10738            "
10739            .unindent()
10740        );
10741        assert_eq!(
10742            editor.selections.ranges::<Point>(cx),
10743            [
10744                Point::new(0, 1)..Point::new(0, 1),
10745                Point::new(1, 1)..Point::new(1, 1),
10746                Point::new(2, 1)..Point::new(2, 1)
10747            ]
10748        );
10749    });
10750}
10751
10752#[gpui::test]
10753async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10754    init_test(cx, |settings| {
10755        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10756    });
10757
10758    let mut cx = EditorTestContext::new(cx).await;
10759
10760    let language = Arc::new(Language::new(
10761        LanguageConfig {
10762            brackets: BracketPairConfig {
10763                pairs: vec![
10764                    BracketPair {
10765                        start: "{".to_string(),
10766                        end: "}".to_string(),
10767                        close: true,
10768                        surround: true,
10769                        newline: true,
10770                    },
10771                    BracketPair {
10772                        start: "(".to_string(),
10773                        end: ")".to_string(),
10774                        close: true,
10775                        surround: true,
10776                        newline: true,
10777                    },
10778                    BracketPair {
10779                        start: "[".to_string(),
10780                        end: "]".to_string(),
10781                        close: false,
10782                        surround: true,
10783                        newline: true,
10784                    },
10785                ],
10786                ..Default::default()
10787            },
10788            autoclose_before: "})]".to_string(),
10789            ..Default::default()
10790        },
10791        Some(tree_sitter_rust::LANGUAGE.into()),
10792    ));
10793
10794    cx.language_registry().add(language.clone());
10795    cx.update_buffer(|buffer, cx| {
10796        buffer.set_language(Some(language), cx);
10797    });
10798
10799    cx.set_state(
10800        &"
10801            {(ˇ)}
10802            [[ˇ]]
10803            {(ˇ)}
10804        "
10805        .unindent(),
10806    );
10807
10808    cx.update_editor(|editor, window, cx| {
10809        editor.backspace(&Default::default(), window, cx);
10810        editor.backspace(&Default::default(), window, cx);
10811    });
10812
10813    cx.assert_editor_state(
10814        &"
10815            ˇ
10816            ˇ]]
10817            ˇ
10818        "
10819        .unindent(),
10820    );
10821
10822    cx.update_editor(|editor, window, cx| {
10823        editor.handle_input("{", window, cx);
10824        editor.handle_input("{", window, cx);
10825        editor.move_right(&MoveRight, window, cx);
10826        editor.move_right(&MoveRight, window, cx);
10827        editor.move_left(&MoveLeft, window, cx);
10828        editor.move_left(&MoveLeft, window, cx);
10829        editor.backspace(&Default::default(), window, cx);
10830    });
10831
10832    cx.assert_editor_state(
10833        &"
10834            {ˇ}
10835            {ˇ}]]
10836            {ˇ}
10837        "
10838        .unindent(),
10839    );
10840
10841    cx.update_editor(|editor, window, cx| {
10842        editor.backspace(&Default::default(), window, cx);
10843    });
10844
10845    cx.assert_editor_state(
10846        &"
10847            ˇ
10848            ˇ]]
10849            ˇ
10850        "
10851        .unindent(),
10852    );
10853}
10854
10855#[gpui::test]
10856async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10857    init_test(cx, |_| {});
10858
10859    let language = Arc::new(Language::new(
10860        LanguageConfig::default(),
10861        Some(tree_sitter_rust::LANGUAGE.into()),
10862    ));
10863
10864    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10865    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10866    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10867    editor
10868        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10869        .await;
10870
10871    editor.update_in(cx, |editor, window, cx| {
10872        editor.set_auto_replace_emoji_shortcode(true);
10873
10874        editor.handle_input("Hello ", window, cx);
10875        editor.handle_input(":wave", window, cx);
10876        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10877
10878        editor.handle_input(":", window, cx);
10879        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10880
10881        editor.handle_input(" :smile", window, cx);
10882        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10883
10884        editor.handle_input(":", window, cx);
10885        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10886
10887        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10888        editor.handle_input(":wave", window, cx);
10889        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10890
10891        editor.handle_input(":", window, cx);
10892        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10893
10894        editor.handle_input(":1", window, cx);
10895        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10896
10897        editor.handle_input(":", window, cx);
10898        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
10899
10900        // Ensure shortcode does not get replaced when it is part of a word
10901        editor.handle_input(" Test:wave", window, cx);
10902        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
10903
10904        editor.handle_input(":", window, cx);
10905        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
10906
10907        editor.set_auto_replace_emoji_shortcode(false);
10908
10909        // Ensure shortcode does not get replaced when auto replace is off
10910        editor.handle_input(" :wave", window, cx);
10911        assert_eq!(
10912            editor.text(cx),
10913            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
10914        );
10915
10916        editor.handle_input(":", window, cx);
10917        assert_eq!(
10918            editor.text(cx),
10919            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
10920        );
10921    });
10922}
10923
10924#[gpui::test]
10925async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
10926    init_test(cx, |_| {});
10927
10928    let (text, insertion_ranges) = marked_text_ranges(
10929        indoc! {"
10930            ˇ
10931        "},
10932        false,
10933    );
10934
10935    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
10936    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10937
10938    _ = editor.update_in(cx, |editor, window, cx| {
10939        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
10940
10941        editor
10942            .insert_snippet(&insertion_ranges, snippet, window, cx)
10943            .unwrap();
10944
10945        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
10946            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
10947            assert_eq!(editor.text(cx), expected_text);
10948            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
10949        }
10950
10951        assert(
10952            editor,
10953            cx,
10954            indoc! {"
10955            type «» =•
10956            "},
10957        );
10958
10959        assert!(editor.context_menu_visible(), "There should be a matches");
10960    });
10961}
10962
10963#[gpui::test]
10964async fn test_snippets(cx: &mut TestAppContext) {
10965    init_test(cx, |_| {});
10966
10967    let mut cx = EditorTestContext::new(cx).await;
10968
10969    cx.set_state(indoc! {"
10970        a.ˇ b
10971        a.ˇ b
10972        a.ˇ b
10973    "});
10974
10975    cx.update_editor(|editor, window, cx| {
10976        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
10977        let insertion_ranges = editor
10978            .selections
10979            .all(cx)
10980            .iter()
10981            .map(|s| s.range())
10982            .collect::<Vec<_>>();
10983        editor
10984            .insert_snippet(&insertion_ranges, snippet, window, cx)
10985            .unwrap();
10986    });
10987
10988    cx.assert_editor_state(indoc! {"
10989        a.f(«oneˇ», two, «threeˇ») b
10990        a.f(«oneˇ», two, «threeˇ») b
10991        a.f(«oneˇ», two, «threeˇ») b
10992    "});
10993
10994    // Can't move earlier than the first tab stop
10995    cx.update_editor(|editor, window, cx| {
10996        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10997    });
10998    cx.assert_editor_state(indoc! {"
10999        a.f(«oneˇ», two, «threeˇ») b
11000        a.f(«oneˇ», two, «threeˇ») b
11001        a.f(«oneˇ», two, «threeˇ») b
11002    "});
11003
11004    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11005    cx.assert_editor_state(indoc! {"
11006        a.f(one, «twoˇ», three) b
11007        a.f(one, «twoˇ», three) b
11008        a.f(one, «twoˇ», three) b
11009    "});
11010
11011    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11012    cx.assert_editor_state(indoc! {"
11013        a.f(«oneˇ», two, «threeˇ») b
11014        a.f(«oneˇ», two, «threeˇ») b
11015        a.f(«oneˇ», two, «threeˇ») b
11016    "});
11017
11018    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11019    cx.assert_editor_state(indoc! {"
11020        a.f(one, «twoˇ», three) b
11021        a.f(one, «twoˇ», three) b
11022        a.f(one, «twoˇ», three) b
11023    "});
11024    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11025    cx.assert_editor_state(indoc! {"
11026        a.f(one, two, three)ˇ b
11027        a.f(one, two, three)ˇ b
11028        a.f(one, two, three)ˇ b
11029    "});
11030
11031    // As soon as the last tab stop is reached, snippet state is gone
11032    cx.update_editor(|editor, window, cx| {
11033        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11034    });
11035    cx.assert_editor_state(indoc! {"
11036        a.f(one, two, three)ˇ b
11037        a.f(one, two, three)ˇ b
11038        a.f(one, two, three)ˇ b
11039    "});
11040}
11041
11042#[gpui::test]
11043async fn test_snippet_indentation(cx: &mut TestAppContext) {
11044    init_test(cx, |_| {});
11045
11046    let mut cx = EditorTestContext::new(cx).await;
11047
11048    cx.update_editor(|editor, window, cx| {
11049        let snippet = Snippet::parse(indoc! {"
11050            /*
11051             * Multiline comment with leading indentation
11052             *
11053             * $1
11054             */
11055            $0"})
11056        .unwrap();
11057        let insertion_ranges = editor
11058            .selections
11059            .all(cx)
11060            .iter()
11061            .map(|s| s.range())
11062            .collect::<Vec<_>>();
11063        editor
11064            .insert_snippet(&insertion_ranges, snippet, window, cx)
11065            .unwrap();
11066    });
11067
11068    cx.assert_editor_state(indoc! {"
11069        /*
11070         * Multiline comment with leading indentation
11071         *
11072         * ˇ
11073         */
11074    "});
11075
11076    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11077    cx.assert_editor_state(indoc! {"
11078        /*
11079         * Multiline comment with leading indentation
11080         *
11081         *•
11082         */
11083        ˇ"});
11084}
11085
11086#[gpui::test]
11087async fn test_document_format_during_save(cx: &mut TestAppContext) {
11088    init_test(cx, |_| {});
11089
11090    let fs = FakeFs::new(cx.executor());
11091    fs.insert_file(path!("/file.rs"), Default::default()).await;
11092
11093    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11094
11095    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11096    language_registry.add(rust_lang());
11097    let mut fake_servers = language_registry.register_fake_lsp(
11098        "Rust",
11099        FakeLspAdapter {
11100            capabilities: lsp::ServerCapabilities {
11101                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11102                ..Default::default()
11103            },
11104            ..Default::default()
11105        },
11106    );
11107
11108    let buffer = project
11109        .update(cx, |project, cx| {
11110            project.open_local_buffer(path!("/file.rs"), cx)
11111        })
11112        .await
11113        .unwrap();
11114
11115    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11116    let (editor, cx) = cx.add_window_view(|window, cx| {
11117        build_editor_with_project(project.clone(), buffer, window, cx)
11118    });
11119    editor.update_in(cx, |editor, window, cx| {
11120        editor.set_text("one\ntwo\nthree\n", window, cx)
11121    });
11122    assert!(cx.read(|cx| editor.is_dirty(cx)));
11123
11124    cx.executor().start_waiting();
11125    let fake_server = fake_servers.next().await.unwrap();
11126
11127    {
11128        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11129            move |params, _| async move {
11130                assert_eq!(
11131                    params.text_document.uri,
11132                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11133                );
11134                assert_eq!(params.options.tab_size, 4);
11135                Ok(Some(vec![lsp::TextEdit::new(
11136                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11137                    ", ".to_string(),
11138                )]))
11139            },
11140        );
11141        let save = editor
11142            .update_in(cx, |editor, window, cx| {
11143                editor.save(
11144                    SaveOptions {
11145                        format: true,
11146                        autosave: false,
11147                    },
11148                    project.clone(),
11149                    window,
11150                    cx,
11151                )
11152            })
11153            .unwrap();
11154        cx.executor().start_waiting();
11155        save.await;
11156
11157        assert_eq!(
11158            editor.update(cx, |editor, cx| editor.text(cx)),
11159            "one, two\nthree\n"
11160        );
11161        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11162    }
11163
11164    {
11165        editor.update_in(cx, |editor, window, cx| {
11166            editor.set_text("one\ntwo\nthree\n", window, cx)
11167        });
11168        assert!(cx.read(|cx| editor.is_dirty(cx)));
11169
11170        // Ensure we can still save even if formatting hangs.
11171        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11172            move |params, _| async move {
11173                assert_eq!(
11174                    params.text_document.uri,
11175                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11176                );
11177                futures::future::pending::<()>().await;
11178                unreachable!()
11179            },
11180        );
11181        let save = editor
11182            .update_in(cx, |editor, window, cx| {
11183                editor.save(
11184                    SaveOptions {
11185                        format: true,
11186                        autosave: false,
11187                    },
11188                    project.clone(),
11189                    window,
11190                    cx,
11191                )
11192            })
11193            .unwrap();
11194        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11195        cx.executor().start_waiting();
11196        save.await;
11197        assert_eq!(
11198            editor.update(cx, |editor, cx| editor.text(cx)),
11199            "one\ntwo\nthree\n"
11200        );
11201    }
11202
11203    // Set rust language override and assert overridden tabsize is sent to language server
11204    update_test_language_settings(cx, |settings| {
11205        settings.languages.0.insert(
11206            "Rust".into(),
11207            LanguageSettingsContent {
11208                tab_size: NonZeroU32::new(8),
11209                ..Default::default()
11210            },
11211        );
11212    });
11213
11214    {
11215        editor.update_in(cx, |editor, window, cx| {
11216            editor.set_text("somehting_new\n", window, cx)
11217        });
11218        assert!(cx.read(|cx| editor.is_dirty(cx)));
11219        let _formatting_request_signal = fake_server
11220            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11221                assert_eq!(
11222                    params.text_document.uri,
11223                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11224                );
11225                assert_eq!(params.options.tab_size, 8);
11226                Ok(Some(vec![]))
11227            });
11228        let save = editor
11229            .update_in(cx, |editor, window, cx| {
11230                editor.save(
11231                    SaveOptions {
11232                        format: true,
11233                        autosave: false,
11234                    },
11235                    project.clone(),
11236                    window,
11237                    cx,
11238                )
11239            })
11240            .unwrap();
11241        cx.executor().start_waiting();
11242        save.await;
11243    }
11244}
11245
11246#[gpui::test]
11247async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11248    init_test(cx, |settings| {
11249        settings.defaults.ensure_final_newline_on_save = Some(false);
11250    });
11251
11252    let fs = FakeFs::new(cx.executor());
11253    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11254
11255    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11256
11257    let buffer = project
11258        .update(cx, |project, cx| {
11259            project.open_local_buffer(path!("/file.txt"), cx)
11260        })
11261        .await
11262        .unwrap();
11263
11264    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11265    let (editor, cx) = cx.add_window_view(|window, cx| {
11266        build_editor_with_project(project.clone(), buffer, window, cx)
11267    });
11268    editor.update_in(cx, |editor, window, cx| {
11269        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11270            s.select_ranges([0..0])
11271        });
11272    });
11273    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11274
11275    editor.update_in(cx, |editor, window, cx| {
11276        editor.handle_input("\n", window, cx)
11277    });
11278    cx.run_until_parked();
11279    save(&editor, &project, cx).await;
11280    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11281
11282    editor.update_in(cx, |editor, window, cx| {
11283        editor.undo(&Default::default(), window, cx);
11284    });
11285    save(&editor, &project, cx).await;
11286    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11287
11288    editor.update_in(cx, |editor, window, cx| {
11289        editor.redo(&Default::default(), window, cx);
11290    });
11291    cx.run_until_parked();
11292    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11293
11294    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11295        let save = editor
11296            .update_in(cx, |editor, window, cx| {
11297                editor.save(
11298                    SaveOptions {
11299                        format: true,
11300                        autosave: false,
11301                    },
11302                    project.clone(),
11303                    window,
11304                    cx,
11305                )
11306            })
11307            .unwrap();
11308        cx.executor().start_waiting();
11309        save.await;
11310        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11311    }
11312}
11313
11314#[gpui::test]
11315async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11316    init_test(cx, |_| {});
11317
11318    let cols = 4;
11319    let rows = 10;
11320    let sample_text_1 = sample_text(rows, cols, 'a');
11321    assert_eq!(
11322        sample_text_1,
11323        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11324    );
11325    let sample_text_2 = sample_text(rows, cols, 'l');
11326    assert_eq!(
11327        sample_text_2,
11328        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11329    );
11330    let sample_text_3 = sample_text(rows, cols, 'v');
11331    assert_eq!(
11332        sample_text_3,
11333        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11334    );
11335
11336    let fs = FakeFs::new(cx.executor());
11337    fs.insert_tree(
11338        path!("/a"),
11339        json!({
11340            "main.rs": sample_text_1,
11341            "other.rs": sample_text_2,
11342            "lib.rs": sample_text_3,
11343        }),
11344    )
11345    .await;
11346
11347    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11348    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11349    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11350
11351    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11352    language_registry.add(rust_lang());
11353    let mut fake_servers = language_registry.register_fake_lsp(
11354        "Rust",
11355        FakeLspAdapter {
11356            capabilities: lsp::ServerCapabilities {
11357                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11358                ..Default::default()
11359            },
11360            ..Default::default()
11361        },
11362    );
11363
11364    let worktree = project.update(cx, |project, cx| {
11365        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11366        assert_eq!(worktrees.len(), 1);
11367        worktrees.pop().unwrap()
11368    });
11369    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11370
11371    let buffer_1 = project
11372        .update(cx, |project, cx| {
11373            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11374        })
11375        .await
11376        .unwrap();
11377    let buffer_2 = project
11378        .update(cx, |project, cx| {
11379            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11380        })
11381        .await
11382        .unwrap();
11383    let buffer_3 = project
11384        .update(cx, |project, cx| {
11385            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11386        })
11387        .await
11388        .unwrap();
11389
11390    let multi_buffer = cx.new(|cx| {
11391        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11392        multi_buffer.push_excerpts(
11393            buffer_1.clone(),
11394            [
11395                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11396                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11397                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11398            ],
11399            cx,
11400        );
11401        multi_buffer.push_excerpts(
11402            buffer_2.clone(),
11403            [
11404                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11405                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11406                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11407            ],
11408            cx,
11409        );
11410        multi_buffer.push_excerpts(
11411            buffer_3.clone(),
11412            [
11413                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11414                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11415                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11416            ],
11417            cx,
11418        );
11419        multi_buffer
11420    });
11421    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11422        Editor::new(
11423            EditorMode::full(),
11424            multi_buffer,
11425            Some(project.clone()),
11426            window,
11427            cx,
11428        )
11429    });
11430
11431    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11432        editor.change_selections(
11433            SelectionEffects::scroll(Autoscroll::Next),
11434            window,
11435            cx,
11436            |s| s.select_ranges(Some(1..2)),
11437        );
11438        editor.insert("|one|two|three|", window, cx);
11439    });
11440    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11441    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11442        editor.change_selections(
11443            SelectionEffects::scroll(Autoscroll::Next),
11444            window,
11445            cx,
11446            |s| s.select_ranges(Some(60..70)),
11447        );
11448        editor.insert("|four|five|six|", window, cx);
11449    });
11450    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11451
11452    // First two buffers should be edited, but not the third one.
11453    assert_eq!(
11454        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11455        "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}",
11456    );
11457    buffer_1.update(cx, |buffer, _| {
11458        assert!(buffer.is_dirty());
11459        assert_eq!(
11460            buffer.text(),
11461            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11462        )
11463    });
11464    buffer_2.update(cx, |buffer, _| {
11465        assert!(buffer.is_dirty());
11466        assert_eq!(
11467            buffer.text(),
11468            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11469        )
11470    });
11471    buffer_3.update(cx, |buffer, _| {
11472        assert!(!buffer.is_dirty());
11473        assert_eq!(buffer.text(), sample_text_3,)
11474    });
11475    cx.executor().run_until_parked();
11476
11477    cx.executor().start_waiting();
11478    let save = multi_buffer_editor
11479        .update_in(cx, |editor, window, cx| {
11480            editor.save(
11481                SaveOptions {
11482                    format: true,
11483                    autosave: false,
11484                },
11485                project.clone(),
11486                window,
11487                cx,
11488            )
11489        })
11490        .unwrap();
11491
11492    let fake_server = fake_servers.next().await.unwrap();
11493    fake_server
11494        .server
11495        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11496            Ok(Some(vec![lsp::TextEdit::new(
11497                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11498                format!("[{} formatted]", params.text_document.uri),
11499            )]))
11500        })
11501        .detach();
11502    save.await;
11503
11504    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11505    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11506    assert_eq!(
11507        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11508        uri!(
11509            "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}"
11510        ),
11511    );
11512    buffer_1.update(cx, |buffer, _| {
11513        assert!(!buffer.is_dirty());
11514        assert_eq!(
11515            buffer.text(),
11516            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11517        )
11518    });
11519    buffer_2.update(cx, |buffer, _| {
11520        assert!(!buffer.is_dirty());
11521        assert_eq!(
11522            buffer.text(),
11523            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11524        )
11525    });
11526    buffer_3.update(cx, |buffer, _| {
11527        assert!(!buffer.is_dirty());
11528        assert_eq!(buffer.text(), sample_text_3,)
11529    });
11530}
11531
11532#[gpui::test]
11533async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11534    init_test(cx, |_| {});
11535
11536    let fs = FakeFs::new(cx.executor());
11537    fs.insert_tree(
11538        path!("/dir"),
11539        json!({
11540            "file1.rs": "fn main() { println!(\"hello\"); }",
11541            "file2.rs": "fn test() { println!(\"test\"); }",
11542            "file3.rs": "fn other() { println!(\"other\"); }\n",
11543        }),
11544    )
11545    .await;
11546
11547    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11548    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11549    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11550
11551    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11552    language_registry.add(rust_lang());
11553
11554    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11555    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11556
11557    // Open three buffers
11558    let buffer_1 = project
11559        .update(cx, |project, cx| {
11560            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
11561        })
11562        .await
11563        .unwrap();
11564    let buffer_2 = project
11565        .update(cx, |project, cx| {
11566            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
11567        })
11568        .await
11569        .unwrap();
11570    let buffer_3 = project
11571        .update(cx, |project, cx| {
11572            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
11573        })
11574        .await
11575        .unwrap();
11576
11577    // Create a multi-buffer with all three buffers
11578    let multi_buffer = cx.new(|cx| {
11579        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11580        multi_buffer.push_excerpts(
11581            buffer_1.clone(),
11582            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11583            cx,
11584        );
11585        multi_buffer.push_excerpts(
11586            buffer_2.clone(),
11587            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11588            cx,
11589        );
11590        multi_buffer.push_excerpts(
11591            buffer_3.clone(),
11592            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11593            cx,
11594        );
11595        multi_buffer
11596    });
11597
11598    let editor = cx.new_window_entity(|window, cx| {
11599        Editor::new(
11600            EditorMode::full(),
11601            multi_buffer,
11602            Some(project.clone()),
11603            window,
11604            cx,
11605        )
11606    });
11607
11608    // Edit only the first buffer
11609    editor.update_in(cx, |editor, window, cx| {
11610        editor.change_selections(
11611            SelectionEffects::scroll(Autoscroll::Next),
11612            window,
11613            cx,
11614            |s| s.select_ranges(Some(10..10)),
11615        );
11616        editor.insert("// edited", window, cx);
11617    });
11618
11619    // Verify that only buffer 1 is dirty
11620    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11621    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11622    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11623
11624    // Get write counts after file creation (files were created with initial content)
11625    // We expect each file to have been written once during creation
11626    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11627    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11628    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11629
11630    // Perform autosave
11631    let save_task = editor.update_in(cx, |editor, window, cx| {
11632        editor.save(
11633            SaveOptions {
11634                format: true,
11635                autosave: true,
11636            },
11637            project.clone(),
11638            window,
11639            cx,
11640        )
11641    });
11642    save_task.await.unwrap();
11643
11644    // Only the dirty buffer should have been saved
11645    assert_eq!(
11646        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11647        1,
11648        "Buffer 1 was dirty, so it should have been written once during autosave"
11649    );
11650    assert_eq!(
11651        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11652        0,
11653        "Buffer 2 was clean, so it should not have been written during autosave"
11654    );
11655    assert_eq!(
11656        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11657        0,
11658        "Buffer 3 was clean, so it should not have been written during autosave"
11659    );
11660
11661    // Verify buffer states after autosave
11662    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11663    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11664    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11665
11666    // Now perform a manual save (format = true)
11667    let save_task = editor.update_in(cx, |editor, window, cx| {
11668        editor.save(
11669            SaveOptions {
11670                format: true,
11671                autosave: false,
11672            },
11673            project.clone(),
11674            window,
11675            cx,
11676        )
11677    });
11678    save_task.await.unwrap();
11679
11680    // During manual save, clean buffers don't get written to disk
11681    // They just get did_save called for language server notifications
11682    assert_eq!(
11683        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11684        1,
11685        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11686    );
11687    assert_eq!(
11688        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11689        0,
11690        "Buffer 2 should not have been written at all"
11691    );
11692    assert_eq!(
11693        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11694        0,
11695        "Buffer 3 should not have been written at all"
11696    );
11697}
11698
11699async fn setup_range_format_test(
11700    cx: &mut TestAppContext,
11701) -> (
11702    Entity<Project>,
11703    Entity<Editor>,
11704    &mut gpui::VisualTestContext,
11705    lsp::FakeLanguageServer,
11706) {
11707    init_test(cx, |_| {});
11708
11709    let fs = FakeFs::new(cx.executor());
11710    fs.insert_file(path!("/file.rs"), Default::default()).await;
11711
11712    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11713
11714    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11715    language_registry.add(rust_lang());
11716    let mut fake_servers = language_registry.register_fake_lsp(
11717        "Rust",
11718        FakeLspAdapter {
11719            capabilities: lsp::ServerCapabilities {
11720                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11721                ..lsp::ServerCapabilities::default()
11722            },
11723            ..FakeLspAdapter::default()
11724        },
11725    );
11726
11727    let buffer = project
11728        .update(cx, |project, cx| {
11729            project.open_local_buffer(path!("/file.rs"), cx)
11730        })
11731        .await
11732        .unwrap();
11733
11734    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11735    let (editor, cx) = cx.add_window_view(|window, cx| {
11736        build_editor_with_project(project.clone(), buffer, window, cx)
11737    });
11738
11739    cx.executor().start_waiting();
11740    let fake_server = fake_servers.next().await.unwrap();
11741
11742    (project, editor, cx, fake_server)
11743}
11744
11745#[gpui::test]
11746async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11747    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11748
11749    editor.update_in(cx, |editor, window, cx| {
11750        editor.set_text("one\ntwo\nthree\n", window, cx)
11751    });
11752    assert!(cx.read(|cx| editor.is_dirty(cx)));
11753
11754    let save = editor
11755        .update_in(cx, |editor, window, cx| {
11756            editor.save(
11757                SaveOptions {
11758                    format: true,
11759                    autosave: false,
11760                },
11761                project.clone(),
11762                window,
11763                cx,
11764            )
11765        })
11766        .unwrap();
11767    fake_server
11768        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11769            assert_eq!(
11770                params.text_document.uri,
11771                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11772            );
11773            assert_eq!(params.options.tab_size, 4);
11774            Ok(Some(vec![lsp::TextEdit::new(
11775                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11776                ", ".to_string(),
11777            )]))
11778        })
11779        .next()
11780        .await;
11781    cx.executor().start_waiting();
11782    save.await;
11783    assert_eq!(
11784        editor.update(cx, |editor, cx| editor.text(cx)),
11785        "one, two\nthree\n"
11786    );
11787    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11788}
11789
11790#[gpui::test]
11791async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11792    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11793
11794    editor.update_in(cx, |editor, window, cx| {
11795        editor.set_text("one\ntwo\nthree\n", window, cx)
11796    });
11797    assert!(cx.read(|cx| editor.is_dirty(cx)));
11798
11799    // Test that save still works when formatting hangs
11800    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11801        move |params, _| async move {
11802            assert_eq!(
11803                params.text_document.uri,
11804                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11805            );
11806            futures::future::pending::<()>().await;
11807            unreachable!()
11808        },
11809    );
11810    let save = editor
11811        .update_in(cx, |editor, window, cx| {
11812            editor.save(
11813                SaveOptions {
11814                    format: true,
11815                    autosave: false,
11816                },
11817                project.clone(),
11818                window,
11819                cx,
11820            )
11821        })
11822        .unwrap();
11823    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11824    cx.executor().start_waiting();
11825    save.await;
11826    assert_eq!(
11827        editor.update(cx, |editor, cx| editor.text(cx)),
11828        "one\ntwo\nthree\n"
11829    );
11830    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11831}
11832
11833#[gpui::test]
11834async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11835    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11836
11837    // Buffer starts clean, no formatting should be requested
11838    let save = editor
11839        .update_in(cx, |editor, window, cx| {
11840            editor.save(
11841                SaveOptions {
11842                    format: false,
11843                    autosave: false,
11844                },
11845                project.clone(),
11846                window,
11847                cx,
11848            )
11849        })
11850        .unwrap();
11851    let _pending_format_request = fake_server
11852        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11853            panic!("Should not be invoked");
11854        })
11855        .next();
11856    cx.executor().start_waiting();
11857    save.await;
11858    cx.run_until_parked();
11859}
11860
11861#[gpui::test]
11862async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11863    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11864
11865    // Set Rust language override and assert overridden tabsize is sent to language server
11866    update_test_language_settings(cx, |settings| {
11867        settings.languages.0.insert(
11868            "Rust".into(),
11869            LanguageSettingsContent {
11870                tab_size: NonZeroU32::new(8),
11871                ..Default::default()
11872            },
11873        );
11874    });
11875
11876    editor.update_in(cx, |editor, window, cx| {
11877        editor.set_text("something_new\n", window, cx)
11878    });
11879    assert!(cx.read(|cx| editor.is_dirty(cx)));
11880    let save = editor
11881        .update_in(cx, |editor, window, cx| {
11882            editor.save(
11883                SaveOptions {
11884                    format: true,
11885                    autosave: false,
11886                },
11887                project.clone(),
11888                window,
11889                cx,
11890            )
11891        })
11892        .unwrap();
11893    fake_server
11894        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11895            assert_eq!(
11896                params.text_document.uri,
11897                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11898            );
11899            assert_eq!(params.options.tab_size, 8);
11900            Ok(Some(Vec::new()))
11901        })
11902        .next()
11903        .await;
11904    save.await;
11905}
11906
11907#[gpui::test]
11908async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
11909    init_test(cx, |settings| {
11910        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
11911            settings::LanguageServerFormatterSpecifier::Current,
11912        )))
11913    });
11914
11915    let fs = FakeFs::new(cx.executor());
11916    fs.insert_file(path!("/file.rs"), Default::default()).await;
11917
11918    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11919
11920    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11921    language_registry.add(Arc::new(Language::new(
11922        LanguageConfig {
11923            name: "Rust".into(),
11924            matcher: LanguageMatcher {
11925                path_suffixes: vec!["rs".to_string()],
11926                ..Default::default()
11927            },
11928            ..LanguageConfig::default()
11929        },
11930        Some(tree_sitter_rust::LANGUAGE.into()),
11931    )));
11932    update_test_language_settings(cx, |settings| {
11933        // Enable Prettier formatting for the same buffer, and ensure
11934        // LSP is called instead of Prettier.
11935        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
11936    });
11937    let mut fake_servers = language_registry.register_fake_lsp(
11938        "Rust",
11939        FakeLspAdapter {
11940            capabilities: lsp::ServerCapabilities {
11941                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11942                ..Default::default()
11943            },
11944            ..Default::default()
11945        },
11946    );
11947
11948    let buffer = project
11949        .update(cx, |project, cx| {
11950            project.open_local_buffer(path!("/file.rs"), cx)
11951        })
11952        .await
11953        .unwrap();
11954
11955    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11956    let (editor, cx) = cx.add_window_view(|window, cx| {
11957        build_editor_with_project(project.clone(), buffer, window, cx)
11958    });
11959    editor.update_in(cx, |editor, window, cx| {
11960        editor.set_text("one\ntwo\nthree\n", window, cx)
11961    });
11962
11963    cx.executor().start_waiting();
11964    let fake_server = fake_servers.next().await.unwrap();
11965
11966    let format = editor
11967        .update_in(cx, |editor, window, cx| {
11968            editor.perform_format(
11969                project.clone(),
11970                FormatTrigger::Manual,
11971                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11972                window,
11973                cx,
11974            )
11975        })
11976        .unwrap();
11977    fake_server
11978        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11979            assert_eq!(
11980                params.text_document.uri,
11981                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11982            );
11983            assert_eq!(params.options.tab_size, 4);
11984            Ok(Some(vec![lsp::TextEdit::new(
11985                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11986                ", ".to_string(),
11987            )]))
11988        })
11989        .next()
11990        .await;
11991    cx.executor().start_waiting();
11992    format.await;
11993    assert_eq!(
11994        editor.update(cx, |editor, cx| editor.text(cx)),
11995        "one, two\nthree\n"
11996    );
11997
11998    editor.update_in(cx, |editor, window, cx| {
11999        editor.set_text("one\ntwo\nthree\n", window, cx)
12000    });
12001    // Ensure we don't lock if formatting hangs.
12002    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12003        move |params, _| async move {
12004            assert_eq!(
12005                params.text_document.uri,
12006                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12007            );
12008            futures::future::pending::<()>().await;
12009            unreachable!()
12010        },
12011    );
12012    let format = editor
12013        .update_in(cx, |editor, window, cx| {
12014            editor.perform_format(
12015                project,
12016                FormatTrigger::Manual,
12017                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12018                window,
12019                cx,
12020            )
12021        })
12022        .unwrap();
12023    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12024    cx.executor().start_waiting();
12025    format.await;
12026    assert_eq!(
12027        editor.update(cx, |editor, cx| editor.text(cx)),
12028        "one\ntwo\nthree\n"
12029    );
12030}
12031
12032#[gpui::test]
12033async fn test_multiple_formatters(cx: &mut TestAppContext) {
12034    init_test(cx, |settings| {
12035        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12036        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12037            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12038            Formatter::CodeAction("code-action-1".into()),
12039            Formatter::CodeAction("code-action-2".into()),
12040        ]))
12041    });
12042
12043    let fs = FakeFs::new(cx.executor());
12044    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12045        .await;
12046
12047    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12048    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12049    language_registry.add(rust_lang());
12050
12051    let mut fake_servers = language_registry.register_fake_lsp(
12052        "Rust",
12053        FakeLspAdapter {
12054            capabilities: lsp::ServerCapabilities {
12055                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12056                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12057                    commands: vec!["the-command-for-code-action-1".into()],
12058                    ..Default::default()
12059                }),
12060                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12061                ..Default::default()
12062            },
12063            ..Default::default()
12064        },
12065    );
12066
12067    let buffer = project
12068        .update(cx, |project, cx| {
12069            project.open_local_buffer(path!("/file.rs"), cx)
12070        })
12071        .await
12072        .unwrap();
12073
12074    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12075    let (editor, cx) = cx.add_window_view(|window, cx| {
12076        build_editor_with_project(project.clone(), buffer, window, cx)
12077    });
12078
12079    cx.executor().start_waiting();
12080
12081    let fake_server = fake_servers.next().await.unwrap();
12082    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12083        move |_params, _| async move {
12084            Ok(Some(vec![lsp::TextEdit::new(
12085                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12086                "applied-formatting\n".to_string(),
12087            )]))
12088        },
12089    );
12090    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12091        move |params, _| async move {
12092            let requested_code_actions = params.context.only.expect("Expected code action request");
12093            assert_eq!(requested_code_actions.len(), 1);
12094
12095            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12096            let code_action = match requested_code_actions[0].as_str() {
12097                "code-action-1" => lsp::CodeAction {
12098                    kind: Some("code-action-1".into()),
12099                    edit: Some(lsp::WorkspaceEdit::new(
12100                        [(
12101                            uri,
12102                            vec![lsp::TextEdit::new(
12103                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12104                                "applied-code-action-1-edit\n".to_string(),
12105                            )],
12106                        )]
12107                        .into_iter()
12108                        .collect(),
12109                    )),
12110                    command: Some(lsp::Command {
12111                        command: "the-command-for-code-action-1".into(),
12112                        ..Default::default()
12113                    }),
12114                    ..Default::default()
12115                },
12116                "code-action-2" => lsp::CodeAction {
12117                    kind: Some("code-action-2".into()),
12118                    edit: Some(lsp::WorkspaceEdit::new(
12119                        [(
12120                            uri,
12121                            vec![lsp::TextEdit::new(
12122                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12123                                "applied-code-action-2-edit\n".to_string(),
12124                            )],
12125                        )]
12126                        .into_iter()
12127                        .collect(),
12128                    )),
12129                    ..Default::default()
12130                },
12131                req => panic!("Unexpected code action request: {:?}", req),
12132            };
12133            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12134                code_action,
12135            )]))
12136        },
12137    );
12138
12139    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12140        move |params, _| async move { Ok(params) }
12141    });
12142
12143    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12144    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12145        let fake = fake_server.clone();
12146        let lock = command_lock.clone();
12147        move |params, _| {
12148            assert_eq!(params.command, "the-command-for-code-action-1");
12149            let fake = fake.clone();
12150            let lock = lock.clone();
12151            async move {
12152                lock.lock().await;
12153                fake.server
12154                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12155                        label: None,
12156                        edit: lsp::WorkspaceEdit {
12157                            changes: Some(
12158                                [(
12159                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12160                                    vec![lsp::TextEdit {
12161                                        range: lsp::Range::new(
12162                                            lsp::Position::new(0, 0),
12163                                            lsp::Position::new(0, 0),
12164                                        ),
12165                                        new_text: "applied-code-action-1-command\n".into(),
12166                                    }],
12167                                )]
12168                                .into_iter()
12169                                .collect(),
12170                            ),
12171                            ..Default::default()
12172                        },
12173                    })
12174                    .await
12175                    .into_response()
12176                    .unwrap();
12177                Ok(Some(json!(null)))
12178            }
12179        }
12180    });
12181
12182    cx.executor().start_waiting();
12183    editor
12184        .update_in(cx, |editor, window, cx| {
12185            editor.perform_format(
12186                project.clone(),
12187                FormatTrigger::Manual,
12188                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12189                window,
12190                cx,
12191            )
12192        })
12193        .unwrap()
12194        .await;
12195    editor.update(cx, |editor, cx| {
12196        assert_eq!(
12197            editor.text(cx),
12198            r#"
12199                applied-code-action-2-edit
12200                applied-code-action-1-command
12201                applied-code-action-1-edit
12202                applied-formatting
12203                one
12204                two
12205                three
12206            "#
12207            .unindent()
12208        );
12209    });
12210
12211    editor.update_in(cx, |editor, window, cx| {
12212        editor.undo(&Default::default(), window, cx);
12213        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12214    });
12215
12216    // Perform a manual edit while waiting for an LSP command
12217    // that's being run as part of a formatting code action.
12218    let lock_guard = command_lock.lock().await;
12219    let format = editor
12220        .update_in(cx, |editor, window, cx| {
12221            editor.perform_format(
12222                project.clone(),
12223                FormatTrigger::Manual,
12224                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12225                window,
12226                cx,
12227            )
12228        })
12229        .unwrap();
12230    cx.run_until_parked();
12231    editor.update(cx, |editor, cx| {
12232        assert_eq!(
12233            editor.text(cx),
12234            r#"
12235                applied-code-action-1-edit
12236                applied-formatting
12237                one
12238                two
12239                three
12240            "#
12241            .unindent()
12242        );
12243
12244        editor.buffer.update(cx, |buffer, cx| {
12245            let ix = buffer.len(cx);
12246            buffer.edit([(ix..ix, "edited\n")], None, cx);
12247        });
12248    });
12249
12250    // Allow the LSP command to proceed. Because the buffer was edited,
12251    // the second code action will not be run.
12252    drop(lock_guard);
12253    format.await;
12254    editor.update_in(cx, |editor, window, cx| {
12255        assert_eq!(
12256            editor.text(cx),
12257            r#"
12258                applied-code-action-1-command
12259                applied-code-action-1-edit
12260                applied-formatting
12261                one
12262                two
12263                three
12264                edited
12265            "#
12266            .unindent()
12267        );
12268
12269        // The manual edit is undone first, because it is the last thing the user did
12270        // (even though the command completed afterwards).
12271        editor.undo(&Default::default(), window, cx);
12272        assert_eq!(
12273            editor.text(cx),
12274            r#"
12275                applied-code-action-1-command
12276                applied-code-action-1-edit
12277                applied-formatting
12278                one
12279                two
12280                three
12281            "#
12282            .unindent()
12283        );
12284
12285        // All the formatting (including the command, which completed after the manual edit)
12286        // is undone together.
12287        editor.undo(&Default::default(), window, cx);
12288        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12289    });
12290}
12291
12292#[gpui::test]
12293async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12294    init_test(cx, |settings| {
12295        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
12296            settings::LanguageServerFormatterSpecifier::Current,
12297        )]))
12298    });
12299
12300    let fs = FakeFs::new(cx.executor());
12301    fs.insert_file(path!("/file.ts"), Default::default()).await;
12302
12303    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12304
12305    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12306    language_registry.add(Arc::new(Language::new(
12307        LanguageConfig {
12308            name: "TypeScript".into(),
12309            matcher: LanguageMatcher {
12310                path_suffixes: vec!["ts".to_string()],
12311                ..Default::default()
12312            },
12313            ..LanguageConfig::default()
12314        },
12315        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12316    )));
12317    update_test_language_settings(cx, |settings| {
12318        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12319    });
12320    let mut fake_servers = language_registry.register_fake_lsp(
12321        "TypeScript",
12322        FakeLspAdapter {
12323            capabilities: lsp::ServerCapabilities {
12324                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12325                ..Default::default()
12326            },
12327            ..Default::default()
12328        },
12329    );
12330
12331    let buffer = project
12332        .update(cx, |project, cx| {
12333            project.open_local_buffer(path!("/file.ts"), cx)
12334        })
12335        .await
12336        .unwrap();
12337
12338    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12339    let (editor, cx) = cx.add_window_view(|window, cx| {
12340        build_editor_with_project(project.clone(), buffer, window, cx)
12341    });
12342    editor.update_in(cx, |editor, window, cx| {
12343        editor.set_text(
12344            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12345            window,
12346            cx,
12347        )
12348    });
12349
12350    cx.executor().start_waiting();
12351    let fake_server = fake_servers.next().await.unwrap();
12352
12353    let format = editor
12354        .update_in(cx, |editor, window, cx| {
12355            editor.perform_code_action_kind(
12356                project.clone(),
12357                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12358                window,
12359                cx,
12360            )
12361        })
12362        .unwrap();
12363    fake_server
12364        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12365            assert_eq!(
12366                params.text_document.uri,
12367                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12368            );
12369            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12370                lsp::CodeAction {
12371                    title: "Organize Imports".to_string(),
12372                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12373                    edit: Some(lsp::WorkspaceEdit {
12374                        changes: Some(
12375                            [(
12376                                params.text_document.uri.clone(),
12377                                vec![lsp::TextEdit::new(
12378                                    lsp::Range::new(
12379                                        lsp::Position::new(1, 0),
12380                                        lsp::Position::new(2, 0),
12381                                    ),
12382                                    "".to_string(),
12383                                )],
12384                            )]
12385                            .into_iter()
12386                            .collect(),
12387                        ),
12388                        ..Default::default()
12389                    }),
12390                    ..Default::default()
12391                },
12392            )]))
12393        })
12394        .next()
12395        .await;
12396    cx.executor().start_waiting();
12397    format.await;
12398    assert_eq!(
12399        editor.update(cx, |editor, cx| editor.text(cx)),
12400        "import { a } from 'module';\n\nconst x = a;\n"
12401    );
12402
12403    editor.update_in(cx, |editor, window, cx| {
12404        editor.set_text(
12405            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12406            window,
12407            cx,
12408        )
12409    });
12410    // Ensure we don't lock if code action hangs.
12411    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12412        move |params, _| async move {
12413            assert_eq!(
12414                params.text_document.uri,
12415                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12416            );
12417            futures::future::pending::<()>().await;
12418            unreachable!()
12419        },
12420    );
12421    let format = editor
12422        .update_in(cx, |editor, window, cx| {
12423            editor.perform_code_action_kind(
12424                project,
12425                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12426                window,
12427                cx,
12428            )
12429        })
12430        .unwrap();
12431    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12432    cx.executor().start_waiting();
12433    format.await;
12434    assert_eq!(
12435        editor.update(cx, |editor, cx| editor.text(cx)),
12436        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12437    );
12438}
12439
12440#[gpui::test]
12441async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12442    init_test(cx, |_| {});
12443
12444    let mut cx = EditorLspTestContext::new_rust(
12445        lsp::ServerCapabilities {
12446            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12447            ..Default::default()
12448        },
12449        cx,
12450    )
12451    .await;
12452
12453    cx.set_state(indoc! {"
12454        one.twoˇ
12455    "});
12456
12457    // The format request takes a long time. When it completes, it inserts
12458    // a newline and an indent before the `.`
12459    cx.lsp
12460        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12461            let executor = cx.background_executor().clone();
12462            async move {
12463                executor.timer(Duration::from_millis(100)).await;
12464                Ok(Some(vec![lsp::TextEdit {
12465                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12466                    new_text: "\n    ".into(),
12467                }]))
12468            }
12469        });
12470
12471    // Submit a format request.
12472    let format_1 = cx
12473        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12474        .unwrap();
12475    cx.executor().run_until_parked();
12476
12477    // Submit a second format request.
12478    let format_2 = cx
12479        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12480        .unwrap();
12481    cx.executor().run_until_parked();
12482
12483    // Wait for both format requests to complete
12484    cx.executor().advance_clock(Duration::from_millis(200));
12485    cx.executor().start_waiting();
12486    format_1.await.unwrap();
12487    cx.executor().start_waiting();
12488    format_2.await.unwrap();
12489
12490    // The formatting edits only happens once.
12491    cx.assert_editor_state(indoc! {"
12492        one
12493            .twoˇ
12494    "});
12495}
12496
12497#[gpui::test]
12498async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12499    init_test(cx, |settings| {
12500        settings.defaults.formatter = Some(FormatterList::default())
12501    });
12502
12503    let mut cx = EditorLspTestContext::new_rust(
12504        lsp::ServerCapabilities {
12505            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12506            ..Default::default()
12507        },
12508        cx,
12509    )
12510    .await;
12511
12512    // Set up a buffer white some trailing whitespace and no trailing newline.
12513    cx.set_state(
12514        &[
12515            "one ",   //
12516            "twoˇ",   //
12517            "three ", //
12518            "four",   //
12519        ]
12520        .join("\n"),
12521    );
12522
12523    // Record which buffer changes have been sent to the language server
12524    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12525    cx.lsp
12526        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12527            let buffer_changes = buffer_changes.clone();
12528            move |params, _| {
12529                buffer_changes.lock().extend(
12530                    params
12531                        .content_changes
12532                        .into_iter()
12533                        .map(|e| (e.range.unwrap(), e.text)),
12534                );
12535            }
12536        });
12537
12538    // Handle formatting requests to the language server.
12539    cx.lsp
12540        .set_request_handler::<lsp::request::Formatting, _, _>({
12541            let buffer_changes = buffer_changes.clone();
12542            move |_, _| {
12543                let buffer_changes = buffer_changes.clone();
12544                // Insert blank lines between each line of the buffer.
12545                async move {
12546                    // When formatting is requested, trailing whitespace has already been stripped,
12547                    // and the trailing newline has already been added.
12548                    assert_eq!(
12549                        &buffer_changes.lock()[1..],
12550                        &[
12551                            (
12552                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12553                                "".into()
12554                            ),
12555                            (
12556                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12557                                "".into()
12558                            ),
12559                            (
12560                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12561                                "\n".into()
12562                            ),
12563                        ]
12564                    );
12565
12566                    Ok(Some(vec![
12567                        lsp::TextEdit {
12568                            range: lsp::Range::new(
12569                                lsp::Position::new(1, 0),
12570                                lsp::Position::new(1, 0),
12571                            ),
12572                            new_text: "\n".into(),
12573                        },
12574                        lsp::TextEdit {
12575                            range: lsp::Range::new(
12576                                lsp::Position::new(2, 0),
12577                                lsp::Position::new(2, 0),
12578                            ),
12579                            new_text: "\n".into(),
12580                        },
12581                    ]))
12582                }
12583            }
12584        });
12585
12586    // Submit a format request.
12587    let format = cx
12588        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12589        .unwrap();
12590
12591    cx.run_until_parked();
12592    // After formatting the buffer, the trailing whitespace is stripped,
12593    // a newline is appended, and the edits provided by the language server
12594    // have been applied.
12595    format.await.unwrap();
12596
12597    cx.assert_editor_state(
12598        &[
12599            "one",   //
12600            "",      //
12601            "twoˇ",  //
12602            "",      //
12603            "three", //
12604            "four",  //
12605            "",      //
12606        ]
12607        .join("\n"),
12608    );
12609
12610    // Undoing the formatting undoes the trailing whitespace removal, the
12611    // trailing newline, and the LSP edits.
12612    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12613    cx.assert_editor_state(
12614        &[
12615            "one ",   //
12616            "twoˇ",   //
12617            "three ", //
12618            "four",   //
12619        ]
12620        .join("\n"),
12621    );
12622}
12623
12624#[gpui::test]
12625async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12626    cx: &mut TestAppContext,
12627) {
12628    init_test(cx, |_| {});
12629
12630    cx.update(|cx| {
12631        cx.update_global::<SettingsStore, _>(|settings, cx| {
12632            settings.update_user_settings(cx, |settings| {
12633                settings.editor.auto_signature_help = Some(true);
12634            });
12635        });
12636    });
12637
12638    let mut cx = EditorLspTestContext::new_rust(
12639        lsp::ServerCapabilities {
12640            signature_help_provider: Some(lsp::SignatureHelpOptions {
12641                ..Default::default()
12642            }),
12643            ..Default::default()
12644        },
12645        cx,
12646    )
12647    .await;
12648
12649    let language = Language::new(
12650        LanguageConfig {
12651            name: "Rust".into(),
12652            brackets: BracketPairConfig {
12653                pairs: vec![
12654                    BracketPair {
12655                        start: "{".to_string(),
12656                        end: "}".to_string(),
12657                        close: true,
12658                        surround: true,
12659                        newline: true,
12660                    },
12661                    BracketPair {
12662                        start: "(".to_string(),
12663                        end: ")".to_string(),
12664                        close: true,
12665                        surround: true,
12666                        newline: true,
12667                    },
12668                    BracketPair {
12669                        start: "/*".to_string(),
12670                        end: " */".to_string(),
12671                        close: true,
12672                        surround: true,
12673                        newline: true,
12674                    },
12675                    BracketPair {
12676                        start: "[".to_string(),
12677                        end: "]".to_string(),
12678                        close: false,
12679                        surround: false,
12680                        newline: true,
12681                    },
12682                    BracketPair {
12683                        start: "\"".to_string(),
12684                        end: "\"".to_string(),
12685                        close: true,
12686                        surround: true,
12687                        newline: false,
12688                    },
12689                    BracketPair {
12690                        start: "<".to_string(),
12691                        end: ">".to_string(),
12692                        close: false,
12693                        surround: true,
12694                        newline: true,
12695                    },
12696                ],
12697                ..Default::default()
12698            },
12699            autoclose_before: "})]".to_string(),
12700            ..Default::default()
12701        },
12702        Some(tree_sitter_rust::LANGUAGE.into()),
12703    );
12704    let language = Arc::new(language);
12705
12706    cx.language_registry().add(language.clone());
12707    cx.update_buffer(|buffer, cx| {
12708        buffer.set_language(Some(language), cx);
12709    });
12710
12711    cx.set_state(
12712        &r#"
12713            fn main() {
12714                sampleˇ
12715            }
12716        "#
12717        .unindent(),
12718    );
12719
12720    cx.update_editor(|editor, window, cx| {
12721        editor.handle_input("(", window, cx);
12722    });
12723    cx.assert_editor_state(
12724        &"
12725            fn main() {
12726                sample(ˇ)
12727            }
12728        "
12729        .unindent(),
12730    );
12731
12732    let mocked_response = lsp::SignatureHelp {
12733        signatures: vec![lsp::SignatureInformation {
12734            label: "fn sample(param1: u8, param2: u8)".to_string(),
12735            documentation: None,
12736            parameters: Some(vec![
12737                lsp::ParameterInformation {
12738                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12739                    documentation: None,
12740                },
12741                lsp::ParameterInformation {
12742                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12743                    documentation: None,
12744                },
12745            ]),
12746            active_parameter: None,
12747        }],
12748        active_signature: Some(0),
12749        active_parameter: Some(0),
12750    };
12751    handle_signature_help_request(&mut cx, mocked_response).await;
12752
12753    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12754        .await;
12755
12756    cx.editor(|editor, _, _| {
12757        let signature_help_state = editor.signature_help_state.popover().cloned();
12758        let signature = signature_help_state.unwrap();
12759        assert_eq!(
12760            signature.signatures[signature.current_signature].label,
12761            "fn sample(param1: u8, param2: u8)"
12762        );
12763    });
12764}
12765
12766#[gpui::test]
12767async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12768    init_test(cx, |_| {});
12769
12770    cx.update(|cx| {
12771        cx.update_global::<SettingsStore, _>(|settings, cx| {
12772            settings.update_user_settings(cx, |settings| {
12773                settings.editor.auto_signature_help = Some(false);
12774                settings.editor.show_signature_help_after_edits = Some(false);
12775            });
12776        });
12777    });
12778
12779    let mut cx = EditorLspTestContext::new_rust(
12780        lsp::ServerCapabilities {
12781            signature_help_provider: Some(lsp::SignatureHelpOptions {
12782                ..Default::default()
12783            }),
12784            ..Default::default()
12785        },
12786        cx,
12787    )
12788    .await;
12789
12790    let language = Language::new(
12791        LanguageConfig {
12792            name: "Rust".into(),
12793            brackets: BracketPairConfig {
12794                pairs: vec![
12795                    BracketPair {
12796                        start: "{".to_string(),
12797                        end: "}".to_string(),
12798                        close: true,
12799                        surround: true,
12800                        newline: true,
12801                    },
12802                    BracketPair {
12803                        start: "(".to_string(),
12804                        end: ")".to_string(),
12805                        close: true,
12806                        surround: true,
12807                        newline: true,
12808                    },
12809                    BracketPair {
12810                        start: "/*".to_string(),
12811                        end: " */".to_string(),
12812                        close: true,
12813                        surround: true,
12814                        newline: true,
12815                    },
12816                    BracketPair {
12817                        start: "[".to_string(),
12818                        end: "]".to_string(),
12819                        close: false,
12820                        surround: false,
12821                        newline: true,
12822                    },
12823                    BracketPair {
12824                        start: "\"".to_string(),
12825                        end: "\"".to_string(),
12826                        close: true,
12827                        surround: true,
12828                        newline: false,
12829                    },
12830                    BracketPair {
12831                        start: "<".to_string(),
12832                        end: ">".to_string(),
12833                        close: false,
12834                        surround: true,
12835                        newline: true,
12836                    },
12837                ],
12838                ..Default::default()
12839            },
12840            autoclose_before: "})]".to_string(),
12841            ..Default::default()
12842        },
12843        Some(tree_sitter_rust::LANGUAGE.into()),
12844    );
12845    let language = Arc::new(language);
12846
12847    cx.language_registry().add(language.clone());
12848    cx.update_buffer(|buffer, cx| {
12849        buffer.set_language(Some(language), cx);
12850    });
12851
12852    // Ensure that signature_help is not called when no signature help is enabled.
12853    cx.set_state(
12854        &r#"
12855            fn main() {
12856                sampleˇ
12857            }
12858        "#
12859        .unindent(),
12860    );
12861    cx.update_editor(|editor, window, cx| {
12862        editor.handle_input("(", window, cx);
12863    });
12864    cx.assert_editor_state(
12865        &"
12866            fn main() {
12867                sample(ˇ)
12868            }
12869        "
12870        .unindent(),
12871    );
12872    cx.editor(|editor, _, _| {
12873        assert!(editor.signature_help_state.task().is_none());
12874    });
12875
12876    let mocked_response = lsp::SignatureHelp {
12877        signatures: vec![lsp::SignatureInformation {
12878            label: "fn sample(param1: u8, param2: u8)".to_string(),
12879            documentation: None,
12880            parameters: Some(vec![
12881                lsp::ParameterInformation {
12882                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12883                    documentation: None,
12884                },
12885                lsp::ParameterInformation {
12886                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12887                    documentation: None,
12888                },
12889            ]),
12890            active_parameter: None,
12891        }],
12892        active_signature: Some(0),
12893        active_parameter: Some(0),
12894    };
12895
12896    // Ensure that signature_help is called when enabled afte edits
12897    cx.update(|_, cx| {
12898        cx.update_global::<SettingsStore, _>(|settings, cx| {
12899            settings.update_user_settings(cx, |settings| {
12900                settings.editor.auto_signature_help = Some(false);
12901                settings.editor.show_signature_help_after_edits = Some(true);
12902            });
12903        });
12904    });
12905    cx.set_state(
12906        &r#"
12907            fn main() {
12908                sampleˇ
12909            }
12910        "#
12911        .unindent(),
12912    );
12913    cx.update_editor(|editor, window, cx| {
12914        editor.handle_input("(", window, cx);
12915    });
12916    cx.assert_editor_state(
12917        &"
12918            fn main() {
12919                sample(ˇ)
12920            }
12921        "
12922        .unindent(),
12923    );
12924    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12925    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12926        .await;
12927    cx.update_editor(|editor, _, _| {
12928        let signature_help_state = editor.signature_help_state.popover().cloned();
12929        assert!(signature_help_state.is_some());
12930        let signature = signature_help_state.unwrap();
12931        assert_eq!(
12932            signature.signatures[signature.current_signature].label,
12933            "fn sample(param1: u8, param2: u8)"
12934        );
12935        editor.signature_help_state = SignatureHelpState::default();
12936    });
12937
12938    // Ensure that signature_help is called when auto signature help override is enabled
12939    cx.update(|_, cx| {
12940        cx.update_global::<SettingsStore, _>(|settings, cx| {
12941            settings.update_user_settings(cx, |settings| {
12942                settings.editor.auto_signature_help = Some(true);
12943                settings.editor.show_signature_help_after_edits = Some(false);
12944            });
12945        });
12946    });
12947    cx.set_state(
12948        &r#"
12949            fn main() {
12950                sampleˇ
12951            }
12952        "#
12953        .unindent(),
12954    );
12955    cx.update_editor(|editor, window, cx| {
12956        editor.handle_input("(", window, cx);
12957    });
12958    cx.assert_editor_state(
12959        &"
12960            fn main() {
12961                sample(ˇ)
12962            }
12963        "
12964        .unindent(),
12965    );
12966    handle_signature_help_request(&mut cx, mocked_response).await;
12967    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12968        .await;
12969    cx.editor(|editor, _, _| {
12970        let signature_help_state = editor.signature_help_state.popover().cloned();
12971        assert!(signature_help_state.is_some());
12972        let signature = signature_help_state.unwrap();
12973        assert_eq!(
12974            signature.signatures[signature.current_signature].label,
12975            "fn sample(param1: u8, param2: u8)"
12976        );
12977    });
12978}
12979
12980#[gpui::test]
12981async fn test_signature_help(cx: &mut TestAppContext) {
12982    init_test(cx, |_| {});
12983    cx.update(|cx| {
12984        cx.update_global::<SettingsStore, _>(|settings, cx| {
12985            settings.update_user_settings(cx, |settings| {
12986                settings.editor.auto_signature_help = Some(true);
12987            });
12988        });
12989    });
12990
12991    let mut cx = EditorLspTestContext::new_rust(
12992        lsp::ServerCapabilities {
12993            signature_help_provider: Some(lsp::SignatureHelpOptions {
12994                ..Default::default()
12995            }),
12996            ..Default::default()
12997        },
12998        cx,
12999    )
13000    .await;
13001
13002    // A test that directly calls `show_signature_help`
13003    cx.update_editor(|editor, window, cx| {
13004        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13005    });
13006
13007    let mocked_response = lsp::SignatureHelp {
13008        signatures: vec![lsp::SignatureInformation {
13009            label: "fn sample(param1: u8, param2: u8)".to_string(),
13010            documentation: None,
13011            parameters: Some(vec![
13012                lsp::ParameterInformation {
13013                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13014                    documentation: None,
13015                },
13016                lsp::ParameterInformation {
13017                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13018                    documentation: None,
13019                },
13020            ]),
13021            active_parameter: None,
13022        }],
13023        active_signature: Some(0),
13024        active_parameter: Some(0),
13025    };
13026    handle_signature_help_request(&mut cx, mocked_response).await;
13027
13028    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13029        .await;
13030
13031    cx.editor(|editor, _, _| {
13032        let signature_help_state = editor.signature_help_state.popover().cloned();
13033        assert!(signature_help_state.is_some());
13034        let signature = signature_help_state.unwrap();
13035        assert_eq!(
13036            signature.signatures[signature.current_signature].label,
13037            "fn sample(param1: u8, param2: u8)"
13038        );
13039    });
13040
13041    // When exiting outside from inside the brackets, `signature_help` is closed.
13042    cx.set_state(indoc! {"
13043        fn main() {
13044            sample(ˇ);
13045        }
13046
13047        fn sample(param1: u8, param2: u8) {}
13048    "});
13049
13050    cx.update_editor(|editor, window, cx| {
13051        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13052            s.select_ranges([0..0])
13053        });
13054    });
13055
13056    let mocked_response = lsp::SignatureHelp {
13057        signatures: Vec::new(),
13058        active_signature: None,
13059        active_parameter: None,
13060    };
13061    handle_signature_help_request(&mut cx, mocked_response).await;
13062
13063    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13064        .await;
13065
13066    cx.editor(|editor, _, _| {
13067        assert!(!editor.signature_help_state.is_shown());
13068    });
13069
13070    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13071    cx.set_state(indoc! {"
13072        fn main() {
13073            sample(ˇ);
13074        }
13075
13076        fn sample(param1: u8, param2: u8) {}
13077    "});
13078
13079    let mocked_response = lsp::SignatureHelp {
13080        signatures: vec![lsp::SignatureInformation {
13081            label: "fn sample(param1: u8, param2: u8)".to_string(),
13082            documentation: None,
13083            parameters: Some(vec![
13084                lsp::ParameterInformation {
13085                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13086                    documentation: None,
13087                },
13088                lsp::ParameterInformation {
13089                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13090                    documentation: None,
13091                },
13092            ]),
13093            active_parameter: None,
13094        }],
13095        active_signature: Some(0),
13096        active_parameter: Some(0),
13097    };
13098    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13099    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13100        .await;
13101    cx.editor(|editor, _, _| {
13102        assert!(editor.signature_help_state.is_shown());
13103    });
13104
13105    // Restore the popover with more parameter input
13106    cx.set_state(indoc! {"
13107        fn main() {
13108            sample(param1, param2ˇ);
13109        }
13110
13111        fn sample(param1: u8, param2: u8) {}
13112    "});
13113
13114    let mocked_response = lsp::SignatureHelp {
13115        signatures: vec![lsp::SignatureInformation {
13116            label: "fn sample(param1: u8, param2: u8)".to_string(),
13117            documentation: None,
13118            parameters: Some(vec![
13119                lsp::ParameterInformation {
13120                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13121                    documentation: None,
13122                },
13123                lsp::ParameterInformation {
13124                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13125                    documentation: None,
13126                },
13127            ]),
13128            active_parameter: None,
13129        }],
13130        active_signature: Some(0),
13131        active_parameter: Some(1),
13132    };
13133    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13134    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13135        .await;
13136
13137    // When selecting a range, the popover is gone.
13138    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13139    cx.update_editor(|editor, window, cx| {
13140        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13141            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13142        })
13143    });
13144    cx.assert_editor_state(indoc! {"
13145        fn main() {
13146            sample(param1, «ˇparam2»);
13147        }
13148
13149        fn sample(param1: u8, param2: u8) {}
13150    "});
13151    cx.editor(|editor, _, _| {
13152        assert!(!editor.signature_help_state.is_shown());
13153    });
13154
13155    // When unselecting again, the popover is back if within the brackets.
13156    cx.update_editor(|editor, window, cx| {
13157        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13158            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13159        })
13160    });
13161    cx.assert_editor_state(indoc! {"
13162        fn main() {
13163            sample(param1, ˇparam2);
13164        }
13165
13166        fn sample(param1: u8, param2: u8) {}
13167    "});
13168    handle_signature_help_request(&mut cx, mocked_response).await;
13169    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13170        .await;
13171    cx.editor(|editor, _, _| {
13172        assert!(editor.signature_help_state.is_shown());
13173    });
13174
13175    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13176    cx.update_editor(|editor, window, cx| {
13177        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13178            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13179            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13180        })
13181    });
13182    cx.assert_editor_state(indoc! {"
13183        fn main() {
13184            sample(param1, ˇparam2);
13185        }
13186
13187        fn sample(param1: u8, param2: u8) {}
13188    "});
13189
13190    let mocked_response = lsp::SignatureHelp {
13191        signatures: vec![lsp::SignatureInformation {
13192            label: "fn sample(param1: u8, param2: u8)".to_string(),
13193            documentation: None,
13194            parameters: Some(vec![
13195                lsp::ParameterInformation {
13196                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13197                    documentation: None,
13198                },
13199                lsp::ParameterInformation {
13200                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13201                    documentation: None,
13202                },
13203            ]),
13204            active_parameter: None,
13205        }],
13206        active_signature: Some(0),
13207        active_parameter: Some(1),
13208    };
13209    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13210    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13211        .await;
13212    cx.update_editor(|editor, _, cx| {
13213        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13214    });
13215    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13216        .await;
13217    cx.update_editor(|editor, window, cx| {
13218        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13219            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13220        })
13221    });
13222    cx.assert_editor_state(indoc! {"
13223        fn main() {
13224            sample(param1, «ˇparam2»);
13225        }
13226
13227        fn sample(param1: u8, param2: u8) {}
13228    "});
13229    cx.update_editor(|editor, window, cx| {
13230        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13231            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13232        })
13233    });
13234    cx.assert_editor_state(indoc! {"
13235        fn main() {
13236            sample(param1, ˇparam2);
13237        }
13238
13239        fn sample(param1: u8, param2: u8) {}
13240    "});
13241    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13242        .await;
13243}
13244
13245#[gpui::test]
13246async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13247    init_test(cx, |_| {});
13248
13249    let mut cx = EditorLspTestContext::new_rust(
13250        lsp::ServerCapabilities {
13251            signature_help_provider: Some(lsp::SignatureHelpOptions {
13252                ..Default::default()
13253            }),
13254            ..Default::default()
13255        },
13256        cx,
13257    )
13258    .await;
13259
13260    cx.set_state(indoc! {"
13261        fn main() {
13262            overloadedˇ
13263        }
13264    "});
13265
13266    cx.update_editor(|editor, window, cx| {
13267        editor.handle_input("(", window, cx);
13268        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13269    });
13270
13271    // Mock response with 3 signatures
13272    let mocked_response = lsp::SignatureHelp {
13273        signatures: vec![
13274            lsp::SignatureInformation {
13275                label: "fn overloaded(x: i32)".to_string(),
13276                documentation: None,
13277                parameters: Some(vec![lsp::ParameterInformation {
13278                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13279                    documentation: None,
13280                }]),
13281                active_parameter: None,
13282            },
13283            lsp::SignatureInformation {
13284                label: "fn overloaded(x: i32, y: i32)".to_string(),
13285                documentation: None,
13286                parameters: Some(vec![
13287                    lsp::ParameterInformation {
13288                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13289                        documentation: None,
13290                    },
13291                    lsp::ParameterInformation {
13292                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13293                        documentation: None,
13294                    },
13295                ]),
13296                active_parameter: None,
13297            },
13298            lsp::SignatureInformation {
13299                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13300                documentation: None,
13301                parameters: Some(vec![
13302                    lsp::ParameterInformation {
13303                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13304                        documentation: None,
13305                    },
13306                    lsp::ParameterInformation {
13307                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13308                        documentation: None,
13309                    },
13310                    lsp::ParameterInformation {
13311                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13312                        documentation: None,
13313                    },
13314                ]),
13315                active_parameter: None,
13316            },
13317        ],
13318        active_signature: Some(1),
13319        active_parameter: Some(0),
13320    };
13321    handle_signature_help_request(&mut cx, mocked_response).await;
13322
13323    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13324        .await;
13325
13326    // Verify we have multiple signatures and the right one is selected
13327    cx.editor(|editor, _, _| {
13328        let popover = editor.signature_help_state.popover().cloned().unwrap();
13329        assert_eq!(popover.signatures.len(), 3);
13330        // active_signature was 1, so that should be the current
13331        assert_eq!(popover.current_signature, 1);
13332        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13333        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13334        assert_eq!(
13335            popover.signatures[2].label,
13336            "fn overloaded(x: i32, y: i32, z: i32)"
13337        );
13338    });
13339
13340    // Test navigation functionality
13341    cx.update_editor(|editor, window, cx| {
13342        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13343    });
13344
13345    cx.editor(|editor, _, _| {
13346        let popover = editor.signature_help_state.popover().cloned().unwrap();
13347        assert_eq!(popover.current_signature, 2);
13348    });
13349
13350    // Test wrap around
13351    cx.update_editor(|editor, window, cx| {
13352        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13353    });
13354
13355    cx.editor(|editor, _, _| {
13356        let popover = editor.signature_help_state.popover().cloned().unwrap();
13357        assert_eq!(popover.current_signature, 0);
13358    });
13359
13360    // Test previous navigation
13361    cx.update_editor(|editor, window, cx| {
13362        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13363    });
13364
13365    cx.editor(|editor, _, _| {
13366        let popover = editor.signature_help_state.popover().cloned().unwrap();
13367        assert_eq!(popover.current_signature, 2);
13368    });
13369}
13370
13371#[gpui::test]
13372async fn test_completion_mode(cx: &mut TestAppContext) {
13373    init_test(cx, |_| {});
13374    let mut cx = EditorLspTestContext::new_rust(
13375        lsp::ServerCapabilities {
13376            completion_provider: Some(lsp::CompletionOptions {
13377                resolve_provider: Some(true),
13378                ..Default::default()
13379            }),
13380            ..Default::default()
13381        },
13382        cx,
13383    )
13384    .await;
13385
13386    struct Run {
13387        run_description: &'static str,
13388        initial_state: String,
13389        buffer_marked_text: String,
13390        completion_label: &'static str,
13391        completion_text: &'static str,
13392        expected_with_insert_mode: String,
13393        expected_with_replace_mode: String,
13394        expected_with_replace_subsequence_mode: String,
13395        expected_with_replace_suffix_mode: String,
13396    }
13397
13398    let runs = [
13399        Run {
13400            run_description: "Start of word matches completion text",
13401            initial_state: "before ediˇ after".into(),
13402            buffer_marked_text: "before <edi|> after".into(),
13403            completion_label: "editor",
13404            completion_text: "editor",
13405            expected_with_insert_mode: "before editorˇ after".into(),
13406            expected_with_replace_mode: "before editorˇ after".into(),
13407            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13408            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13409        },
13410        Run {
13411            run_description: "Accept same text at the middle of the word",
13412            initial_state: "before ediˇtor after".into(),
13413            buffer_marked_text: "before <edi|tor> after".into(),
13414            completion_label: "editor",
13415            completion_text: "editor",
13416            expected_with_insert_mode: "before editorˇtor after".into(),
13417            expected_with_replace_mode: "before editorˇ after".into(),
13418            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13419            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13420        },
13421        Run {
13422            run_description: "End of word matches completion text -- cursor at end",
13423            initial_state: "before torˇ after".into(),
13424            buffer_marked_text: "before <tor|> after".into(),
13425            completion_label: "editor",
13426            completion_text: "editor",
13427            expected_with_insert_mode: "before editorˇ after".into(),
13428            expected_with_replace_mode: "before editorˇ after".into(),
13429            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13430            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13431        },
13432        Run {
13433            run_description: "End of word matches completion text -- cursor at start",
13434            initial_state: "before ˇtor after".into(),
13435            buffer_marked_text: "before <|tor> after".into(),
13436            completion_label: "editor",
13437            completion_text: "editor",
13438            expected_with_insert_mode: "before editorˇtor after".into(),
13439            expected_with_replace_mode: "before editorˇ after".into(),
13440            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13441            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13442        },
13443        Run {
13444            run_description: "Prepend text containing whitespace",
13445            initial_state: "pˇfield: bool".into(),
13446            buffer_marked_text: "<p|field>: bool".into(),
13447            completion_label: "pub ",
13448            completion_text: "pub ",
13449            expected_with_insert_mode: "pub ˇfield: bool".into(),
13450            expected_with_replace_mode: "pub ˇ: bool".into(),
13451            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13452            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13453        },
13454        Run {
13455            run_description: "Add element to start of list",
13456            initial_state: "[element_ˇelement_2]".into(),
13457            buffer_marked_text: "[<element_|element_2>]".into(),
13458            completion_label: "element_1",
13459            completion_text: "element_1",
13460            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13461            expected_with_replace_mode: "[element_1ˇ]".into(),
13462            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13463            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13464        },
13465        Run {
13466            run_description: "Add element to start of list -- first and second elements are equal",
13467            initial_state: "[elˇelement]".into(),
13468            buffer_marked_text: "[<el|element>]".into(),
13469            completion_label: "element",
13470            completion_text: "element",
13471            expected_with_insert_mode: "[elementˇelement]".into(),
13472            expected_with_replace_mode: "[elementˇ]".into(),
13473            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13474            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13475        },
13476        Run {
13477            run_description: "Ends with matching suffix",
13478            initial_state: "SubˇError".into(),
13479            buffer_marked_text: "<Sub|Error>".into(),
13480            completion_label: "SubscriptionError",
13481            completion_text: "SubscriptionError",
13482            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13483            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13484            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13485            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13486        },
13487        Run {
13488            run_description: "Suffix is a subsequence -- contiguous",
13489            initial_state: "SubˇErr".into(),
13490            buffer_marked_text: "<Sub|Err>".into(),
13491            completion_label: "SubscriptionError",
13492            completion_text: "SubscriptionError",
13493            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13494            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13495            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13496            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13497        },
13498        Run {
13499            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13500            initial_state: "Suˇscrirr".into(),
13501            buffer_marked_text: "<Su|scrirr>".into(),
13502            completion_label: "SubscriptionError",
13503            completion_text: "SubscriptionError",
13504            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13505            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13506            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13507            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13508        },
13509        Run {
13510            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13511            initial_state: "foo(indˇix)".into(),
13512            buffer_marked_text: "foo(<ind|ix>)".into(),
13513            completion_label: "node_index",
13514            completion_text: "node_index",
13515            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13516            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13517            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13518            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13519        },
13520        Run {
13521            run_description: "Replace range ends before cursor - should extend to cursor",
13522            initial_state: "before editˇo after".into(),
13523            buffer_marked_text: "before <{ed}>it|o after".into(),
13524            completion_label: "editor",
13525            completion_text: "editor",
13526            expected_with_insert_mode: "before editorˇo after".into(),
13527            expected_with_replace_mode: "before editorˇo after".into(),
13528            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13529            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13530        },
13531        Run {
13532            run_description: "Uses label for suffix matching",
13533            initial_state: "before ediˇtor after".into(),
13534            buffer_marked_text: "before <edi|tor> after".into(),
13535            completion_label: "editor",
13536            completion_text: "editor()",
13537            expected_with_insert_mode: "before editor()ˇtor after".into(),
13538            expected_with_replace_mode: "before editor()ˇ after".into(),
13539            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13540            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13541        },
13542        Run {
13543            run_description: "Case insensitive subsequence and suffix matching",
13544            initial_state: "before EDiˇtoR after".into(),
13545            buffer_marked_text: "before <EDi|toR> after".into(),
13546            completion_label: "editor",
13547            completion_text: "editor",
13548            expected_with_insert_mode: "before editorˇtoR after".into(),
13549            expected_with_replace_mode: "before editorˇ after".into(),
13550            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13551            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13552        },
13553    ];
13554
13555    for run in runs {
13556        let run_variations = [
13557            (LspInsertMode::Insert, run.expected_with_insert_mode),
13558            (LspInsertMode::Replace, run.expected_with_replace_mode),
13559            (
13560                LspInsertMode::ReplaceSubsequence,
13561                run.expected_with_replace_subsequence_mode,
13562            ),
13563            (
13564                LspInsertMode::ReplaceSuffix,
13565                run.expected_with_replace_suffix_mode,
13566            ),
13567        ];
13568
13569        for (lsp_insert_mode, expected_text) in run_variations {
13570            eprintln!(
13571                "run = {:?}, mode = {lsp_insert_mode:.?}",
13572                run.run_description,
13573            );
13574
13575            update_test_language_settings(&mut cx, |settings| {
13576                settings.defaults.completions = Some(CompletionSettingsContent {
13577                    lsp_insert_mode: Some(lsp_insert_mode),
13578                    words: Some(WordsCompletionMode::Disabled),
13579                    words_min_length: Some(0),
13580                    ..Default::default()
13581                });
13582            });
13583
13584            cx.set_state(&run.initial_state);
13585            cx.update_editor(|editor, window, cx| {
13586                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13587            });
13588
13589            let counter = Arc::new(AtomicUsize::new(0));
13590            handle_completion_request_with_insert_and_replace(
13591                &mut cx,
13592                &run.buffer_marked_text,
13593                vec![(run.completion_label, run.completion_text)],
13594                counter.clone(),
13595            )
13596            .await;
13597            cx.condition(|editor, _| editor.context_menu_visible())
13598                .await;
13599            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13600
13601            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13602                editor
13603                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13604                    .unwrap()
13605            });
13606            cx.assert_editor_state(&expected_text);
13607            handle_resolve_completion_request(&mut cx, None).await;
13608            apply_additional_edits.await.unwrap();
13609        }
13610    }
13611}
13612
13613#[gpui::test]
13614async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13615    init_test(cx, |_| {});
13616    let mut cx = EditorLspTestContext::new_rust(
13617        lsp::ServerCapabilities {
13618            completion_provider: Some(lsp::CompletionOptions {
13619                resolve_provider: Some(true),
13620                ..Default::default()
13621            }),
13622            ..Default::default()
13623        },
13624        cx,
13625    )
13626    .await;
13627
13628    let initial_state = "SubˇError";
13629    let buffer_marked_text = "<Sub|Error>";
13630    let completion_text = "SubscriptionError";
13631    let expected_with_insert_mode = "SubscriptionErrorˇError";
13632    let expected_with_replace_mode = "SubscriptionErrorˇ";
13633
13634    update_test_language_settings(&mut cx, |settings| {
13635        settings.defaults.completions = Some(CompletionSettingsContent {
13636            words: Some(WordsCompletionMode::Disabled),
13637            words_min_length: Some(0),
13638            // set the opposite here to ensure that the action is overriding the default behavior
13639            lsp_insert_mode: Some(LspInsertMode::Insert),
13640            ..Default::default()
13641        });
13642    });
13643
13644    cx.set_state(initial_state);
13645    cx.update_editor(|editor, window, cx| {
13646        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13647    });
13648
13649    let counter = Arc::new(AtomicUsize::new(0));
13650    handle_completion_request_with_insert_and_replace(
13651        &mut cx,
13652        buffer_marked_text,
13653        vec![(completion_text, completion_text)],
13654        counter.clone(),
13655    )
13656    .await;
13657    cx.condition(|editor, _| editor.context_menu_visible())
13658        .await;
13659    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13660
13661    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13662        editor
13663            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13664            .unwrap()
13665    });
13666    cx.assert_editor_state(expected_with_replace_mode);
13667    handle_resolve_completion_request(&mut cx, None).await;
13668    apply_additional_edits.await.unwrap();
13669
13670    update_test_language_settings(&mut cx, |settings| {
13671        settings.defaults.completions = Some(CompletionSettingsContent {
13672            words: Some(WordsCompletionMode::Disabled),
13673            words_min_length: Some(0),
13674            // set the opposite here to ensure that the action is overriding the default behavior
13675            lsp_insert_mode: Some(LspInsertMode::Replace),
13676            ..Default::default()
13677        });
13678    });
13679
13680    cx.set_state(initial_state);
13681    cx.update_editor(|editor, window, cx| {
13682        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13683    });
13684    handle_completion_request_with_insert_and_replace(
13685        &mut cx,
13686        buffer_marked_text,
13687        vec![(completion_text, completion_text)],
13688        counter.clone(),
13689    )
13690    .await;
13691    cx.condition(|editor, _| editor.context_menu_visible())
13692        .await;
13693    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13694
13695    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13696        editor
13697            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13698            .unwrap()
13699    });
13700    cx.assert_editor_state(expected_with_insert_mode);
13701    handle_resolve_completion_request(&mut cx, None).await;
13702    apply_additional_edits.await.unwrap();
13703}
13704
13705#[gpui::test]
13706async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13707    init_test(cx, |_| {});
13708    let mut cx = EditorLspTestContext::new_rust(
13709        lsp::ServerCapabilities {
13710            completion_provider: Some(lsp::CompletionOptions {
13711                resolve_provider: Some(true),
13712                ..Default::default()
13713            }),
13714            ..Default::default()
13715        },
13716        cx,
13717    )
13718    .await;
13719
13720    // scenario: surrounding text matches completion text
13721    let completion_text = "to_offset";
13722    let initial_state = indoc! {"
13723        1. buf.to_offˇsuffix
13724        2. buf.to_offˇsuf
13725        3. buf.to_offˇfix
13726        4. buf.to_offˇ
13727        5. into_offˇensive
13728        6. ˇsuffix
13729        7. let ˇ //
13730        8. aaˇzz
13731        9. buf.to_off«zzzzzˇ»suffix
13732        10. buf.«ˇzzzzz»suffix
13733        11. to_off«ˇzzzzz»
13734
13735        buf.to_offˇsuffix  // newest cursor
13736    "};
13737    let completion_marked_buffer = indoc! {"
13738        1. buf.to_offsuffix
13739        2. buf.to_offsuf
13740        3. buf.to_offfix
13741        4. buf.to_off
13742        5. into_offensive
13743        6. suffix
13744        7. let  //
13745        8. aazz
13746        9. buf.to_offzzzzzsuffix
13747        10. buf.zzzzzsuffix
13748        11. to_offzzzzz
13749
13750        buf.<to_off|suffix>  // newest cursor
13751    "};
13752    let expected = indoc! {"
13753        1. buf.to_offsetˇ
13754        2. buf.to_offsetˇsuf
13755        3. buf.to_offsetˇfix
13756        4. buf.to_offsetˇ
13757        5. into_offsetˇensive
13758        6. to_offsetˇsuffix
13759        7. let to_offsetˇ //
13760        8. aato_offsetˇzz
13761        9. buf.to_offsetˇ
13762        10. buf.to_offsetˇsuffix
13763        11. to_offsetˇ
13764
13765        buf.to_offsetˇ  // newest cursor
13766    "};
13767    cx.set_state(initial_state);
13768    cx.update_editor(|editor, window, cx| {
13769        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13770    });
13771    handle_completion_request_with_insert_and_replace(
13772        &mut cx,
13773        completion_marked_buffer,
13774        vec![(completion_text, completion_text)],
13775        Arc::new(AtomicUsize::new(0)),
13776    )
13777    .await;
13778    cx.condition(|editor, _| editor.context_menu_visible())
13779        .await;
13780    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13781        editor
13782            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13783            .unwrap()
13784    });
13785    cx.assert_editor_state(expected);
13786    handle_resolve_completion_request(&mut cx, None).await;
13787    apply_additional_edits.await.unwrap();
13788
13789    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13790    let completion_text = "foo_and_bar";
13791    let initial_state = indoc! {"
13792        1. ooanbˇ
13793        2. zooanbˇ
13794        3. ooanbˇz
13795        4. zooanbˇz
13796        5. ooanˇ
13797        6. oanbˇ
13798
13799        ooanbˇ
13800    "};
13801    let completion_marked_buffer = indoc! {"
13802        1. ooanb
13803        2. zooanb
13804        3. ooanbz
13805        4. zooanbz
13806        5. ooan
13807        6. oanb
13808
13809        <ooanb|>
13810    "};
13811    let expected = indoc! {"
13812        1. foo_and_barˇ
13813        2. zfoo_and_barˇ
13814        3. foo_and_barˇz
13815        4. zfoo_and_barˇz
13816        5. ooanfoo_and_barˇ
13817        6. oanbfoo_and_barˇ
13818
13819        foo_and_barˇ
13820    "};
13821    cx.set_state(initial_state);
13822    cx.update_editor(|editor, window, cx| {
13823        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13824    });
13825    handle_completion_request_with_insert_and_replace(
13826        &mut cx,
13827        completion_marked_buffer,
13828        vec![(completion_text, completion_text)],
13829        Arc::new(AtomicUsize::new(0)),
13830    )
13831    .await;
13832    cx.condition(|editor, _| editor.context_menu_visible())
13833        .await;
13834    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13835        editor
13836            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13837            .unwrap()
13838    });
13839    cx.assert_editor_state(expected);
13840    handle_resolve_completion_request(&mut cx, None).await;
13841    apply_additional_edits.await.unwrap();
13842
13843    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13844    // (expects the same as if it was inserted at the end)
13845    let completion_text = "foo_and_bar";
13846    let initial_state = indoc! {"
13847        1. ooˇanb
13848        2. zooˇanb
13849        3. ooˇanbz
13850        4. zooˇanbz
13851
13852        ooˇanb
13853    "};
13854    let completion_marked_buffer = indoc! {"
13855        1. ooanb
13856        2. zooanb
13857        3. ooanbz
13858        4. zooanbz
13859
13860        <oo|anb>
13861    "};
13862    let expected = indoc! {"
13863        1. foo_and_barˇ
13864        2. zfoo_and_barˇ
13865        3. foo_and_barˇz
13866        4. zfoo_and_barˇz
13867
13868        foo_and_barˇ
13869    "};
13870    cx.set_state(initial_state);
13871    cx.update_editor(|editor, window, cx| {
13872        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13873    });
13874    handle_completion_request_with_insert_and_replace(
13875        &mut cx,
13876        completion_marked_buffer,
13877        vec![(completion_text, completion_text)],
13878        Arc::new(AtomicUsize::new(0)),
13879    )
13880    .await;
13881    cx.condition(|editor, _| editor.context_menu_visible())
13882        .await;
13883    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13884        editor
13885            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13886            .unwrap()
13887    });
13888    cx.assert_editor_state(expected);
13889    handle_resolve_completion_request(&mut cx, None).await;
13890    apply_additional_edits.await.unwrap();
13891}
13892
13893// This used to crash
13894#[gpui::test]
13895async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13896    init_test(cx, |_| {});
13897
13898    let buffer_text = indoc! {"
13899        fn main() {
13900            10.satu;
13901
13902            //
13903            // separate cursors so they open in different excerpts (manually reproducible)
13904            //
13905
13906            10.satu20;
13907        }
13908    "};
13909    let multibuffer_text_with_selections = indoc! {"
13910        fn main() {
13911            10.satuˇ;
13912
13913            //
13914
13915            //
13916
13917            10.satuˇ20;
13918        }
13919    "};
13920    let expected_multibuffer = indoc! {"
13921        fn main() {
13922            10.saturating_sub()ˇ;
13923
13924            //
13925
13926            //
13927
13928            10.saturating_sub()ˇ;
13929        }
13930    "};
13931
13932    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13933    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13934
13935    let fs = FakeFs::new(cx.executor());
13936    fs.insert_tree(
13937        path!("/a"),
13938        json!({
13939            "main.rs": buffer_text,
13940        }),
13941    )
13942    .await;
13943
13944    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13945    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13946    language_registry.add(rust_lang());
13947    let mut fake_servers = language_registry.register_fake_lsp(
13948        "Rust",
13949        FakeLspAdapter {
13950            capabilities: lsp::ServerCapabilities {
13951                completion_provider: Some(lsp::CompletionOptions {
13952                    resolve_provider: None,
13953                    ..lsp::CompletionOptions::default()
13954                }),
13955                ..lsp::ServerCapabilities::default()
13956            },
13957            ..FakeLspAdapter::default()
13958        },
13959    );
13960    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13961    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13962    let buffer = project
13963        .update(cx, |project, cx| {
13964            project.open_local_buffer(path!("/a/main.rs"), cx)
13965        })
13966        .await
13967        .unwrap();
13968
13969    let multi_buffer = cx.new(|cx| {
13970        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13971        multi_buffer.push_excerpts(
13972            buffer.clone(),
13973            [ExcerptRange::new(0..first_excerpt_end)],
13974            cx,
13975        );
13976        multi_buffer.push_excerpts(
13977            buffer.clone(),
13978            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13979            cx,
13980        );
13981        multi_buffer
13982    });
13983
13984    let editor = workspace
13985        .update(cx, |_, window, cx| {
13986            cx.new(|cx| {
13987                Editor::new(
13988                    EditorMode::Full {
13989                        scale_ui_elements_with_buffer_font_size: false,
13990                        show_active_line_background: false,
13991                        sized_by_content: false,
13992                    },
13993                    multi_buffer.clone(),
13994                    Some(project.clone()),
13995                    window,
13996                    cx,
13997                )
13998            })
13999        })
14000        .unwrap();
14001
14002    let pane = workspace
14003        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14004        .unwrap();
14005    pane.update_in(cx, |pane, window, cx| {
14006        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14007    });
14008
14009    let fake_server = fake_servers.next().await.unwrap();
14010
14011    editor.update_in(cx, |editor, window, cx| {
14012        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14013            s.select_ranges([
14014                Point::new(1, 11)..Point::new(1, 11),
14015                Point::new(7, 11)..Point::new(7, 11),
14016            ])
14017        });
14018
14019        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14020    });
14021
14022    editor.update_in(cx, |editor, window, cx| {
14023        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14024    });
14025
14026    fake_server
14027        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14028            let completion_item = lsp::CompletionItem {
14029                label: "saturating_sub()".into(),
14030                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14031                    lsp::InsertReplaceEdit {
14032                        new_text: "saturating_sub()".to_owned(),
14033                        insert: lsp::Range::new(
14034                            lsp::Position::new(7, 7),
14035                            lsp::Position::new(7, 11),
14036                        ),
14037                        replace: lsp::Range::new(
14038                            lsp::Position::new(7, 7),
14039                            lsp::Position::new(7, 13),
14040                        ),
14041                    },
14042                )),
14043                ..lsp::CompletionItem::default()
14044            };
14045
14046            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14047        })
14048        .next()
14049        .await
14050        .unwrap();
14051
14052    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14053        .await;
14054
14055    editor
14056        .update_in(cx, |editor, window, cx| {
14057            editor
14058                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14059                .unwrap()
14060        })
14061        .await
14062        .unwrap();
14063
14064    editor.update(cx, |editor, cx| {
14065        assert_text_with_selections(editor, expected_multibuffer, cx);
14066    })
14067}
14068
14069#[gpui::test]
14070async fn test_completion(cx: &mut TestAppContext) {
14071    init_test(cx, |_| {});
14072
14073    let mut cx = EditorLspTestContext::new_rust(
14074        lsp::ServerCapabilities {
14075            completion_provider: Some(lsp::CompletionOptions {
14076                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14077                resolve_provider: Some(true),
14078                ..Default::default()
14079            }),
14080            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14081            ..Default::default()
14082        },
14083        cx,
14084    )
14085    .await;
14086    let counter = Arc::new(AtomicUsize::new(0));
14087
14088    cx.set_state(indoc! {"
14089        oneˇ
14090        two
14091        three
14092    "});
14093    cx.simulate_keystroke(".");
14094    handle_completion_request(
14095        indoc! {"
14096            one.|<>
14097            two
14098            three
14099        "},
14100        vec!["first_completion", "second_completion"],
14101        true,
14102        counter.clone(),
14103        &mut cx,
14104    )
14105    .await;
14106    cx.condition(|editor, _| editor.context_menu_visible())
14107        .await;
14108    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14109
14110    let _handler = handle_signature_help_request(
14111        &mut cx,
14112        lsp::SignatureHelp {
14113            signatures: vec![lsp::SignatureInformation {
14114                label: "test signature".to_string(),
14115                documentation: None,
14116                parameters: Some(vec![lsp::ParameterInformation {
14117                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14118                    documentation: None,
14119                }]),
14120                active_parameter: None,
14121            }],
14122            active_signature: None,
14123            active_parameter: None,
14124        },
14125    );
14126    cx.update_editor(|editor, window, cx| {
14127        assert!(
14128            !editor.signature_help_state.is_shown(),
14129            "No signature help was called for"
14130        );
14131        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14132    });
14133    cx.run_until_parked();
14134    cx.update_editor(|editor, _, _| {
14135        assert!(
14136            !editor.signature_help_state.is_shown(),
14137            "No signature help should be shown when completions menu is open"
14138        );
14139    });
14140
14141    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14142        editor.context_menu_next(&Default::default(), window, cx);
14143        editor
14144            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14145            .unwrap()
14146    });
14147    cx.assert_editor_state(indoc! {"
14148        one.second_completionˇ
14149        two
14150        three
14151    "});
14152
14153    handle_resolve_completion_request(
14154        &mut cx,
14155        Some(vec![
14156            (
14157                //This overlaps with the primary completion edit which is
14158                //misbehavior from the LSP spec, test that we filter it out
14159                indoc! {"
14160                    one.second_ˇcompletion
14161                    two
14162                    threeˇ
14163                "},
14164                "overlapping additional edit",
14165            ),
14166            (
14167                indoc! {"
14168                    one.second_completion
14169                    two
14170                    threeˇ
14171                "},
14172                "\nadditional edit",
14173            ),
14174        ]),
14175    )
14176    .await;
14177    apply_additional_edits.await.unwrap();
14178    cx.assert_editor_state(indoc! {"
14179        one.second_completionˇ
14180        two
14181        three
14182        additional edit
14183    "});
14184
14185    cx.set_state(indoc! {"
14186        one.second_completion
14187        twoˇ
14188        threeˇ
14189        additional edit
14190    "});
14191    cx.simulate_keystroke(" ");
14192    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14193    cx.simulate_keystroke("s");
14194    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14195
14196    cx.assert_editor_state(indoc! {"
14197        one.second_completion
14198        two sˇ
14199        three sˇ
14200        additional edit
14201    "});
14202    handle_completion_request(
14203        indoc! {"
14204            one.second_completion
14205            two s
14206            three <s|>
14207            additional edit
14208        "},
14209        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14210        true,
14211        counter.clone(),
14212        &mut cx,
14213    )
14214    .await;
14215    cx.condition(|editor, _| editor.context_menu_visible())
14216        .await;
14217    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14218
14219    cx.simulate_keystroke("i");
14220
14221    handle_completion_request(
14222        indoc! {"
14223            one.second_completion
14224            two si
14225            three <si|>
14226            additional edit
14227        "},
14228        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14229        true,
14230        counter.clone(),
14231        &mut cx,
14232    )
14233    .await;
14234    cx.condition(|editor, _| editor.context_menu_visible())
14235        .await;
14236    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14237
14238    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14239        editor
14240            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14241            .unwrap()
14242    });
14243    cx.assert_editor_state(indoc! {"
14244        one.second_completion
14245        two sixth_completionˇ
14246        three sixth_completionˇ
14247        additional edit
14248    "});
14249
14250    apply_additional_edits.await.unwrap();
14251
14252    update_test_language_settings(&mut cx, |settings| {
14253        settings.defaults.show_completions_on_input = Some(false);
14254    });
14255    cx.set_state("editorˇ");
14256    cx.simulate_keystroke(".");
14257    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14258    cx.simulate_keystrokes("c l o");
14259    cx.assert_editor_state("editor.cloˇ");
14260    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14261    cx.update_editor(|editor, window, cx| {
14262        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14263    });
14264    handle_completion_request(
14265        "editor.<clo|>",
14266        vec!["close", "clobber"],
14267        true,
14268        counter.clone(),
14269        &mut cx,
14270    )
14271    .await;
14272    cx.condition(|editor, _| editor.context_menu_visible())
14273        .await;
14274    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14275
14276    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14277        editor
14278            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14279            .unwrap()
14280    });
14281    cx.assert_editor_state("editor.clobberˇ");
14282    handle_resolve_completion_request(&mut cx, None).await;
14283    apply_additional_edits.await.unwrap();
14284}
14285
14286#[gpui::test]
14287async fn test_completion_reuse(cx: &mut TestAppContext) {
14288    init_test(cx, |_| {});
14289
14290    let mut cx = EditorLspTestContext::new_rust(
14291        lsp::ServerCapabilities {
14292            completion_provider: Some(lsp::CompletionOptions {
14293                trigger_characters: Some(vec![".".to_string()]),
14294                ..Default::default()
14295            }),
14296            ..Default::default()
14297        },
14298        cx,
14299    )
14300    .await;
14301
14302    let counter = Arc::new(AtomicUsize::new(0));
14303    cx.set_state("objˇ");
14304    cx.simulate_keystroke(".");
14305
14306    // Initial completion request returns complete results
14307    let is_incomplete = false;
14308    handle_completion_request(
14309        "obj.|<>",
14310        vec!["a", "ab", "abc"],
14311        is_incomplete,
14312        counter.clone(),
14313        &mut cx,
14314    )
14315    .await;
14316    cx.run_until_parked();
14317    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14318    cx.assert_editor_state("obj.ˇ");
14319    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14320
14321    // Type "a" - filters existing completions
14322    cx.simulate_keystroke("a");
14323    cx.run_until_parked();
14324    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14325    cx.assert_editor_state("obj.aˇ");
14326    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14327
14328    // Type "b" - filters existing completions
14329    cx.simulate_keystroke("b");
14330    cx.run_until_parked();
14331    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14332    cx.assert_editor_state("obj.abˇ");
14333    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14334
14335    // Type "c" - filters existing completions
14336    cx.simulate_keystroke("c");
14337    cx.run_until_parked();
14338    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14339    cx.assert_editor_state("obj.abcˇ");
14340    check_displayed_completions(vec!["abc"], &mut cx);
14341
14342    // Backspace to delete "c" - filters existing completions
14343    cx.update_editor(|editor, window, cx| {
14344        editor.backspace(&Backspace, window, cx);
14345    });
14346    cx.run_until_parked();
14347    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14348    cx.assert_editor_state("obj.abˇ");
14349    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14350
14351    // Moving cursor to the left dismisses menu.
14352    cx.update_editor(|editor, window, cx| {
14353        editor.move_left(&MoveLeft, window, cx);
14354    });
14355    cx.run_until_parked();
14356    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14357    cx.assert_editor_state("obj.aˇb");
14358    cx.update_editor(|editor, _, _| {
14359        assert_eq!(editor.context_menu_visible(), false);
14360    });
14361
14362    // Type "b" - new request
14363    cx.simulate_keystroke("b");
14364    let is_incomplete = false;
14365    handle_completion_request(
14366        "obj.<ab|>a",
14367        vec!["ab", "abc"],
14368        is_incomplete,
14369        counter.clone(),
14370        &mut cx,
14371    )
14372    .await;
14373    cx.run_until_parked();
14374    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14375    cx.assert_editor_state("obj.abˇb");
14376    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14377
14378    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14379    cx.update_editor(|editor, window, cx| {
14380        editor.backspace(&Backspace, window, cx);
14381    });
14382    let is_incomplete = false;
14383    handle_completion_request(
14384        "obj.<a|>b",
14385        vec!["a", "ab", "abc"],
14386        is_incomplete,
14387        counter.clone(),
14388        &mut cx,
14389    )
14390    .await;
14391    cx.run_until_parked();
14392    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14393    cx.assert_editor_state("obj.aˇb");
14394    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14395
14396    // Backspace to delete "a" - dismisses menu.
14397    cx.update_editor(|editor, window, cx| {
14398        editor.backspace(&Backspace, window, cx);
14399    });
14400    cx.run_until_parked();
14401    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14402    cx.assert_editor_state("obj.ˇb");
14403    cx.update_editor(|editor, _, _| {
14404        assert_eq!(editor.context_menu_visible(), false);
14405    });
14406}
14407
14408#[gpui::test]
14409async fn test_word_completion(cx: &mut TestAppContext) {
14410    let lsp_fetch_timeout_ms = 10;
14411    init_test(cx, |language_settings| {
14412        language_settings.defaults.completions = Some(CompletionSettingsContent {
14413            words_min_length: Some(0),
14414            lsp_fetch_timeout_ms: Some(10),
14415            lsp_insert_mode: Some(LspInsertMode::Insert),
14416            ..Default::default()
14417        });
14418    });
14419
14420    let mut cx = EditorLspTestContext::new_rust(
14421        lsp::ServerCapabilities {
14422            completion_provider: Some(lsp::CompletionOptions {
14423                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14424                ..lsp::CompletionOptions::default()
14425            }),
14426            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14427            ..lsp::ServerCapabilities::default()
14428        },
14429        cx,
14430    )
14431    .await;
14432
14433    let throttle_completions = Arc::new(AtomicBool::new(false));
14434
14435    let lsp_throttle_completions = throttle_completions.clone();
14436    let _completion_requests_handler =
14437        cx.lsp
14438            .server
14439            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14440                let lsp_throttle_completions = lsp_throttle_completions.clone();
14441                let cx = cx.clone();
14442                async move {
14443                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14444                        cx.background_executor()
14445                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14446                            .await;
14447                    }
14448                    Ok(Some(lsp::CompletionResponse::Array(vec![
14449                        lsp::CompletionItem {
14450                            label: "first".into(),
14451                            ..lsp::CompletionItem::default()
14452                        },
14453                        lsp::CompletionItem {
14454                            label: "last".into(),
14455                            ..lsp::CompletionItem::default()
14456                        },
14457                    ])))
14458                }
14459            });
14460
14461    cx.set_state(indoc! {"
14462        oneˇ
14463        two
14464        three
14465    "});
14466    cx.simulate_keystroke(".");
14467    cx.executor().run_until_parked();
14468    cx.condition(|editor, _| editor.context_menu_visible())
14469        .await;
14470    cx.update_editor(|editor, window, cx| {
14471        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14472        {
14473            assert_eq!(
14474                completion_menu_entries(menu),
14475                &["first", "last"],
14476                "When LSP server is fast to reply, no fallback word completions are used"
14477            );
14478        } else {
14479            panic!("expected completion menu to be open");
14480        }
14481        editor.cancel(&Cancel, window, cx);
14482    });
14483    cx.executor().run_until_parked();
14484    cx.condition(|editor, _| !editor.context_menu_visible())
14485        .await;
14486
14487    throttle_completions.store(true, atomic::Ordering::Release);
14488    cx.simulate_keystroke(".");
14489    cx.executor()
14490        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14491    cx.executor().run_until_parked();
14492    cx.condition(|editor, _| editor.context_menu_visible())
14493        .await;
14494    cx.update_editor(|editor, _, _| {
14495        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14496        {
14497            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14498                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14499        } else {
14500            panic!("expected completion menu to be open");
14501        }
14502    });
14503}
14504
14505#[gpui::test]
14506async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14507    init_test(cx, |language_settings| {
14508        language_settings.defaults.completions = Some(CompletionSettingsContent {
14509            words: Some(WordsCompletionMode::Enabled),
14510            words_min_length: Some(0),
14511            lsp_insert_mode: Some(LspInsertMode::Insert),
14512            ..Default::default()
14513        });
14514    });
14515
14516    let mut cx = EditorLspTestContext::new_rust(
14517        lsp::ServerCapabilities {
14518            completion_provider: Some(lsp::CompletionOptions {
14519                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14520                ..lsp::CompletionOptions::default()
14521            }),
14522            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14523            ..lsp::ServerCapabilities::default()
14524        },
14525        cx,
14526    )
14527    .await;
14528
14529    let _completion_requests_handler =
14530        cx.lsp
14531            .server
14532            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14533                Ok(Some(lsp::CompletionResponse::Array(vec![
14534                    lsp::CompletionItem {
14535                        label: "first".into(),
14536                        ..lsp::CompletionItem::default()
14537                    },
14538                    lsp::CompletionItem {
14539                        label: "last".into(),
14540                        ..lsp::CompletionItem::default()
14541                    },
14542                ])))
14543            });
14544
14545    cx.set_state(indoc! {"ˇ
14546        first
14547        last
14548        second
14549    "});
14550    cx.simulate_keystroke(".");
14551    cx.executor().run_until_parked();
14552    cx.condition(|editor, _| editor.context_menu_visible())
14553        .await;
14554    cx.update_editor(|editor, _, _| {
14555        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14556        {
14557            assert_eq!(
14558                completion_menu_entries(menu),
14559                &["first", "last", "second"],
14560                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14561            );
14562        } else {
14563            panic!("expected completion menu to be open");
14564        }
14565    });
14566}
14567
14568#[gpui::test]
14569async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14570    init_test(cx, |language_settings| {
14571        language_settings.defaults.completions = Some(CompletionSettingsContent {
14572            words: Some(WordsCompletionMode::Disabled),
14573            words_min_length: Some(0),
14574            lsp_insert_mode: Some(LspInsertMode::Insert),
14575            ..Default::default()
14576        });
14577    });
14578
14579    let mut cx = EditorLspTestContext::new_rust(
14580        lsp::ServerCapabilities {
14581            completion_provider: Some(lsp::CompletionOptions {
14582                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14583                ..lsp::CompletionOptions::default()
14584            }),
14585            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14586            ..lsp::ServerCapabilities::default()
14587        },
14588        cx,
14589    )
14590    .await;
14591
14592    let _completion_requests_handler =
14593        cx.lsp
14594            .server
14595            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14596                panic!("LSP completions should not be queried when dealing with word completions")
14597            });
14598
14599    cx.set_state(indoc! {"ˇ
14600        first
14601        last
14602        second
14603    "});
14604    cx.update_editor(|editor, window, cx| {
14605        editor.show_word_completions(&ShowWordCompletions, window, cx);
14606    });
14607    cx.executor().run_until_parked();
14608    cx.condition(|editor, _| editor.context_menu_visible())
14609        .await;
14610    cx.update_editor(|editor, _, _| {
14611        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14612        {
14613            assert_eq!(
14614                completion_menu_entries(menu),
14615                &["first", "last", "second"],
14616                "`ShowWordCompletions` action should show word completions"
14617            );
14618        } else {
14619            panic!("expected completion menu to be open");
14620        }
14621    });
14622
14623    cx.simulate_keystroke("l");
14624    cx.executor().run_until_parked();
14625    cx.condition(|editor, _| editor.context_menu_visible())
14626        .await;
14627    cx.update_editor(|editor, _, _| {
14628        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14629        {
14630            assert_eq!(
14631                completion_menu_entries(menu),
14632                &["last"],
14633                "After showing word completions, further editing should filter them and not query the LSP"
14634            );
14635        } else {
14636            panic!("expected completion menu to be open");
14637        }
14638    });
14639}
14640
14641#[gpui::test]
14642async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14643    init_test(cx, |language_settings| {
14644        language_settings.defaults.completions = Some(CompletionSettingsContent {
14645            words_min_length: Some(0),
14646            lsp: Some(false),
14647            lsp_insert_mode: Some(LspInsertMode::Insert),
14648            ..Default::default()
14649        });
14650    });
14651
14652    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14653
14654    cx.set_state(indoc! {"ˇ
14655        0_usize
14656        let
14657        33
14658        4.5f32
14659    "});
14660    cx.update_editor(|editor, window, cx| {
14661        editor.show_completions(&ShowCompletions::default(), window, cx);
14662    });
14663    cx.executor().run_until_parked();
14664    cx.condition(|editor, _| editor.context_menu_visible())
14665        .await;
14666    cx.update_editor(|editor, window, cx| {
14667        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14668        {
14669            assert_eq!(
14670                completion_menu_entries(menu),
14671                &["let"],
14672                "With no digits in the completion query, no digits should be in the word completions"
14673            );
14674        } else {
14675            panic!("expected completion menu to be open");
14676        }
14677        editor.cancel(&Cancel, window, cx);
14678    });
14679
14680    cx.set_state(indoc! {"14681        0_usize
14682        let
14683        3
14684        33.35f32
14685    "});
14686    cx.update_editor(|editor, window, cx| {
14687        editor.show_completions(&ShowCompletions::default(), window, cx);
14688    });
14689    cx.executor().run_until_parked();
14690    cx.condition(|editor, _| editor.context_menu_visible())
14691        .await;
14692    cx.update_editor(|editor, _, _| {
14693        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14694        {
14695            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14696                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14697        } else {
14698            panic!("expected completion menu to be open");
14699        }
14700    });
14701}
14702
14703#[gpui::test]
14704async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14705    init_test(cx, |language_settings| {
14706        language_settings.defaults.completions = Some(CompletionSettingsContent {
14707            words: Some(WordsCompletionMode::Enabled),
14708            words_min_length: Some(3),
14709            lsp_insert_mode: Some(LspInsertMode::Insert),
14710            ..Default::default()
14711        });
14712    });
14713
14714    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14715    cx.set_state(indoc! {"ˇ
14716        wow
14717        wowen
14718        wowser
14719    "});
14720    cx.simulate_keystroke("w");
14721    cx.executor().run_until_parked();
14722    cx.update_editor(|editor, _, _| {
14723        if editor.context_menu.borrow_mut().is_some() {
14724            panic!(
14725                "expected completion menu to be hidden, as words completion threshold is not met"
14726            );
14727        }
14728    });
14729
14730    cx.update_editor(|editor, window, cx| {
14731        editor.show_word_completions(&ShowWordCompletions, window, cx);
14732    });
14733    cx.executor().run_until_parked();
14734    cx.update_editor(|editor, window, cx| {
14735        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14736        {
14737            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");
14738        } else {
14739            panic!("expected completion menu to be open after the word completions are called with an action");
14740        }
14741
14742        editor.cancel(&Cancel, window, cx);
14743    });
14744    cx.update_editor(|editor, _, _| {
14745        if editor.context_menu.borrow_mut().is_some() {
14746            panic!("expected completion menu to be hidden after canceling");
14747        }
14748    });
14749
14750    cx.simulate_keystroke("o");
14751    cx.executor().run_until_parked();
14752    cx.update_editor(|editor, _, _| {
14753        if editor.context_menu.borrow_mut().is_some() {
14754            panic!(
14755                "expected completion menu to be hidden, as words completion threshold is not met still"
14756            );
14757        }
14758    });
14759
14760    cx.simulate_keystroke("w");
14761    cx.executor().run_until_parked();
14762    cx.update_editor(|editor, _, _| {
14763        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14764        {
14765            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14766        } else {
14767            panic!("expected completion menu to be open after the word completions threshold is met");
14768        }
14769    });
14770}
14771
14772#[gpui::test]
14773async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14774    init_test(cx, |language_settings| {
14775        language_settings.defaults.completions = Some(CompletionSettingsContent {
14776            words: Some(WordsCompletionMode::Enabled),
14777            words_min_length: Some(0),
14778            lsp_insert_mode: Some(LspInsertMode::Insert),
14779            ..Default::default()
14780        });
14781    });
14782
14783    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14784    cx.update_editor(|editor, _, _| {
14785        editor.disable_word_completions();
14786    });
14787    cx.set_state(indoc! {"ˇ
14788        wow
14789        wowen
14790        wowser
14791    "});
14792    cx.simulate_keystroke("w");
14793    cx.executor().run_until_parked();
14794    cx.update_editor(|editor, _, _| {
14795        if editor.context_menu.borrow_mut().is_some() {
14796            panic!(
14797                "expected completion menu to be hidden, as words completion are disabled for this editor"
14798            );
14799        }
14800    });
14801
14802    cx.update_editor(|editor, window, cx| {
14803        editor.show_word_completions(&ShowWordCompletions, window, cx);
14804    });
14805    cx.executor().run_until_parked();
14806    cx.update_editor(|editor, _, _| {
14807        if editor.context_menu.borrow_mut().is_some() {
14808            panic!(
14809                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14810            );
14811        }
14812    });
14813}
14814
14815fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14816    let position = || lsp::Position {
14817        line: params.text_document_position.position.line,
14818        character: params.text_document_position.position.character,
14819    };
14820    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14821        range: lsp::Range {
14822            start: position(),
14823            end: position(),
14824        },
14825        new_text: text.to_string(),
14826    }))
14827}
14828
14829#[gpui::test]
14830async fn test_multiline_completion(cx: &mut TestAppContext) {
14831    init_test(cx, |_| {});
14832
14833    let fs = FakeFs::new(cx.executor());
14834    fs.insert_tree(
14835        path!("/a"),
14836        json!({
14837            "main.ts": "a",
14838        }),
14839    )
14840    .await;
14841
14842    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14843    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14844    let typescript_language = Arc::new(Language::new(
14845        LanguageConfig {
14846            name: "TypeScript".into(),
14847            matcher: LanguageMatcher {
14848                path_suffixes: vec!["ts".to_string()],
14849                ..LanguageMatcher::default()
14850            },
14851            line_comments: vec!["// ".into()],
14852            ..LanguageConfig::default()
14853        },
14854        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14855    ));
14856    language_registry.add(typescript_language.clone());
14857    let mut fake_servers = language_registry.register_fake_lsp(
14858        "TypeScript",
14859        FakeLspAdapter {
14860            capabilities: lsp::ServerCapabilities {
14861                completion_provider: Some(lsp::CompletionOptions {
14862                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14863                    ..lsp::CompletionOptions::default()
14864                }),
14865                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14866                ..lsp::ServerCapabilities::default()
14867            },
14868            // Emulate vtsls label generation
14869            label_for_completion: Some(Box::new(|item, _| {
14870                let text = if let Some(description) = item
14871                    .label_details
14872                    .as_ref()
14873                    .and_then(|label_details| label_details.description.as_ref())
14874                {
14875                    format!("{} {}", item.label, description)
14876                } else if let Some(detail) = &item.detail {
14877                    format!("{} {}", item.label, detail)
14878                } else {
14879                    item.label.clone()
14880                };
14881                Some(language::CodeLabel::plain(text, None))
14882            })),
14883            ..FakeLspAdapter::default()
14884        },
14885    );
14886    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14887    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14888    let worktree_id = workspace
14889        .update(cx, |workspace, _window, cx| {
14890            workspace.project().update(cx, |project, cx| {
14891                project.worktrees(cx).next().unwrap().read(cx).id()
14892            })
14893        })
14894        .unwrap();
14895    let _buffer = project
14896        .update(cx, |project, cx| {
14897            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14898        })
14899        .await
14900        .unwrap();
14901    let editor = workspace
14902        .update(cx, |workspace, window, cx| {
14903            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
14904        })
14905        .unwrap()
14906        .await
14907        .unwrap()
14908        .downcast::<Editor>()
14909        .unwrap();
14910    let fake_server = fake_servers.next().await.unwrap();
14911
14912    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14913    let multiline_label_2 = "a\nb\nc\n";
14914    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14915    let multiline_description = "d\ne\nf\n";
14916    let multiline_detail_2 = "g\nh\ni\n";
14917
14918    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14919        move |params, _| async move {
14920            Ok(Some(lsp::CompletionResponse::Array(vec![
14921                lsp::CompletionItem {
14922                    label: multiline_label.to_string(),
14923                    text_edit: gen_text_edit(&params, "new_text_1"),
14924                    ..lsp::CompletionItem::default()
14925                },
14926                lsp::CompletionItem {
14927                    label: "single line label 1".to_string(),
14928                    detail: Some(multiline_detail.to_string()),
14929                    text_edit: gen_text_edit(&params, "new_text_2"),
14930                    ..lsp::CompletionItem::default()
14931                },
14932                lsp::CompletionItem {
14933                    label: "single line label 2".to_string(),
14934                    label_details: Some(lsp::CompletionItemLabelDetails {
14935                        description: Some(multiline_description.to_string()),
14936                        detail: None,
14937                    }),
14938                    text_edit: gen_text_edit(&params, "new_text_2"),
14939                    ..lsp::CompletionItem::default()
14940                },
14941                lsp::CompletionItem {
14942                    label: multiline_label_2.to_string(),
14943                    detail: Some(multiline_detail_2.to_string()),
14944                    text_edit: gen_text_edit(&params, "new_text_3"),
14945                    ..lsp::CompletionItem::default()
14946                },
14947                lsp::CompletionItem {
14948                    label: "Label with many     spaces and \t but without newlines".to_string(),
14949                    detail: Some(
14950                        "Details with many     spaces and \t but without newlines".to_string(),
14951                    ),
14952                    text_edit: gen_text_edit(&params, "new_text_4"),
14953                    ..lsp::CompletionItem::default()
14954                },
14955            ])))
14956        },
14957    );
14958
14959    editor.update_in(cx, |editor, window, cx| {
14960        cx.focus_self(window);
14961        editor.move_to_end(&MoveToEnd, window, cx);
14962        editor.handle_input(".", window, cx);
14963    });
14964    cx.run_until_parked();
14965    completion_handle.next().await.unwrap();
14966
14967    editor.update(cx, |editor, _| {
14968        assert!(editor.context_menu_visible());
14969        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14970        {
14971            let completion_labels = menu
14972                .completions
14973                .borrow()
14974                .iter()
14975                .map(|c| c.label.text.clone())
14976                .collect::<Vec<_>>();
14977            assert_eq!(
14978                completion_labels,
14979                &[
14980                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14981                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14982                    "single line label 2 d e f ",
14983                    "a b c g h i ",
14984                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14985                ],
14986                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14987            );
14988
14989            for completion in menu
14990                .completions
14991                .borrow()
14992                .iter() {
14993                    assert_eq!(
14994                        completion.label.filter_range,
14995                        0..completion.label.text.len(),
14996                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14997                    );
14998                }
14999        } else {
15000            panic!("expected completion menu to be open");
15001        }
15002    });
15003}
15004
15005#[gpui::test]
15006async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15007    init_test(cx, |_| {});
15008    let mut cx = EditorLspTestContext::new_rust(
15009        lsp::ServerCapabilities {
15010            completion_provider: Some(lsp::CompletionOptions {
15011                trigger_characters: Some(vec![".".to_string()]),
15012                ..Default::default()
15013            }),
15014            ..Default::default()
15015        },
15016        cx,
15017    )
15018    .await;
15019    cx.lsp
15020        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15021            Ok(Some(lsp::CompletionResponse::Array(vec![
15022                lsp::CompletionItem {
15023                    label: "first".into(),
15024                    ..Default::default()
15025                },
15026                lsp::CompletionItem {
15027                    label: "last".into(),
15028                    ..Default::default()
15029                },
15030            ])))
15031        });
15032    cx.set_state("variableˇ");
15033    cx.simulate_keystroke(".");
15034    cx.executor().run_until_parked();
15035
15036    cx.update_editor(|editor, _, _| {
15037        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15038        {
15039            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15040        } else {
15041            panic!("expected completion menu to be open");
15042        }
15043    });
15044
15045    cx.update_editor(|editor, window, cx| {
15046        editor.move_page_down(&MovePageDown::default(), window, cx);
15047        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15048        {
15049            assert!(
15050                menu.selected_item == 1,
15051                "expected PageDown to select the last item from the context menu"
15052            );
15053        } else {
15054            panic!("expected completion menu to stay open after PageDown");
15055        }
15056    });
15057
15058    cx.update_editor(|editor, window, cx| {
15059        editor.move_page_up(&MovePageUp::default(), window, cx);
15060        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15061        {
15062            assert!(
15063                menu.selected_item == 0,
15064                "expected PageUp to select the first item from the context menu"
15065            );
15066        } else {
15067            panic!("expected completion menu to stay open after PageUp");
15068        }
15069    });
15070}
15071
15072#[gpui::test]
15073async fn test_as_is_completions(cx: &mut TestAppContext) {
15074    init_test(cx, |_| {});
15075    let mut cx = EditorLspTestContext::new_rust(
15076        lsp::ServerCapabilities {
15077            completion_provider: Some(lsp::CompletionOptions {
15078                ..Default::default()
15079            }),
15080            ..Default::default()
15081        },
15082        cx,
15083    )
15084    .await;
15085    cx.lsp
15086        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15087            Ok(Some(lsp::CompletionResponse::Array(vec![
15088                lsp::CompletionItem {
15089                    label: "unsafe".into(),
15090                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15091                        range: lsp::Range {
15092                            start: lsp::Position {
15093                                line: 1,
15094                                character: 2,
15095                            },
15096                            end: lsp::Position {
15097                                line: 1,
15098                                character: 3,
15099                            },
15100                        },
15101                        new_text: "unsafe".to_string(),
15102                    })),
15103                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15104                    ..Default::default()
15105                },
15106            ])))
15107        });
15108    cx.set_state("fn a() {}\n");
15109    cx.executor().run_until_parked();
15110    cx.update_editor(|editor, window, cx| {
15111        editor.show_completions(
15112            &ShowCompletions {
15113                trigger: Some("\n".into()),
15114            },
15115            window,
15116            cx,
15117        );
15118    });
15119    cx.executor().run_until_parked();
15120
15121    cx.update_editor(|editor, window, cx| {
15122        editor.confirm_completion(&Default::default(), window, cx)
15123    });
15124    cx.executor().run_until_parked();
15125    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15126}
15127
15128#[gpui::test]
15129async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15130    init_test(cx, |_| {});
15131    let language =
15132        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15133    let mut cx = EditorLspTestContext::new(
15134        language,
15135        lsp::ServerCapabilities {
15136            completion_provider: Some(lsp::CompletionOptions {
15137                ..lsp::CompletionOptions::default()
15138            }),
15139            ..lsp::ServerCapabilities::default()
15140        },
15141        cx,
15142    )
15143    .await;
15144
15145    cx.set_state(
15146        "#ifndef BAR_H
15147#define BAR_H
15148
15149#include <stdbool.h>
15150
15151int fn_branch(bool do_branch1, bool do_branch2);
15152
15153#endif // BAR_H
15154ˇ",
15155    );
15156    cx.executor().run_until_parked();
15157    cx.update_editor(|editor, window, cx| {
15158        editor.handle_input("#", window, cx);
15159    });
15160    cx.executor().run_until_parked();
15161    cx.update_editor(|editor, window, cx| {
15162        editor.handle_input("i", window, cx);
15163    });
15164    cx.executor().run_until_parked();
15165    cx.update_editor(|editor, window, cx| {
15166        editor.handle_input("n", window, cx);
15167    });
15168    cx.executor().run_until_parked();
15169    cx.assert_editor_state(
15170        "#ifndef BAR_H
15171#define BAR_H
15172
15173#include <stdbool.h>
15174
15175int fn_branch(bool do_branch1, bool do_branch2);
15176
15177#endif // BAR_H
15178#inˇ",
15179    );
15180
15181    cx.lsp
15182        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15183            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15184                is_incomplete: false,
15185                item_defaults: None,
15186                items: vec![lsp::CompletionItem {
15187                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15188                    label_details: Some(lsp::CompletionItemLabelDetails {
15189                        detail: Some("header".to_string()),
15190                        description: None,
15191                    }),
15192                    label: " include".to_string(),
15193                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15194                        range: lsp::Range {
15195                            start: lsp::Position {
15196                                line: 8,
15197                                character: 1,
15198                            },
15199                            end: lsp::Position {
15200                                line: 8,
15201                                character: 1,
15202                            },
15203                        },
15204                        new_text: "include \"$0\"".to_string(),
15205                    })),
15206                    sort_text: Some("40b67681include".to_string()),
15207                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15208                    filter_text: Some("include".to_string()),
15209                    insert_text: Some("include \"$0\"".to_string()),
15210                    ..lsp::CompletionItem::default()
15211                }],
15212            })))
15213        });
15214    cx.update_editor(|editor, window, cx| {
15215        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15216    });
15217    cx.executor().run_until_parked();
15218    cx.update_editor(|editor, window, cx| {
15219        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15220    });
15221    cx.executor().run_until_parked();
15222    cx.assert_editor_state(
15223        "#ifndef BAR_H
15224#define BAR_H
15225
15226#include <stdbool.h>
15227
15228int fn_branch(bool do_branch1, bool do_branch2);
15229
15230#endif // BAR_H
15231#include \"ˇ\"",
15232    );
15233
15234    cx.lsp
15235        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15236            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15237                is_incomplete: true,
15238                item_defaults: None,
15239                items: vec![lsp::CompletionItem {
15240                    kind: Some(lsp::CompletionItemKind::FILE),
15241                    label: "AGL/".to_string(),
15242                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15243                        range: lsp::Range {
15244                            start: lsp::Position {
15245                                line: 8,
15246                                character: 10,
15247                            },
15248                            end: lsp::Position {
15249                                line: 8,
15250                                character: 11,
15251                            },
15252                        },
15253                        new_text: "AGL/".to_string(),
15254                    })),
15255                    sort_text: Some("40b67681AGL/".to_string()),
15256                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15257                    filter_text: Some("AGL/".to_string()),
15258                    insert_text: Some("AGL/".to_string()),
15259                    ..lsp::CompletionItem::default()
15260                }],
15261            })))
15262        });
15263    cx.update_editor(|editor, window, cx| {
15264        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15265    });
15266    cx.executor().run_until_parked();
15267    cx.update_editor(|editor, window, cx| {
15268        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15269    });
15270    cx.executor().run_until_parked();
15271    cx.assert_editor_state(
15272        r##"#ifndef BAR_H
15273#define BAR_H
15274
15275#include <stdbool.h>
15276
15277int fn_branch(bool do_branch1, bool do_branch2);
15278
15279#endif // BAR_H
15280#include "AGL/ˇ"##,
15281    );
15282
15283    cx.update_editor(|editor, window, cx| {
15284        editor.handle_input("\"", window, cx);
15285    });
15286    cx.executor().run_until_parked();
15287    cx.assert_editor_state(
15288        r##"#ifndef BAR_H
15289#define BAR_H
15290
15291#include <stdbool.h>
15292
15293int fn_branch(bool do_branch1, bool do_branch2);
15294
15295#endif // BAR_H
15296#include "AGL/"ˇ"##,
15297    );
15298}
15299
15300#[gpui::test]
15301async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15302    init_test(cx, |_| {});
15303
15304    let mut cx = EditorLspTestContext::new_rust(
15305        lsp::ServerCapabilities {
15306            completion_provider: Some(lsp::CompletionOptions {
15307                trigger_characters: Some(vec![".".to_string()]),
15308                resolve_provider: Some(true),
15309                ..Default::default()
15310            }),
15311            ..Default::default()
15312        },
15313        cx,
15314    )
15315    .await;
15316
15317    cx.set_state("fn main() { let a = 2ˇ; }");
15318    cx.simulate_keystroke(".");
15319    let completion_item = lsp::CompletionItem {
15320        label: "Some".into(),
15321        kind: Some(lsp::CompletionItemKind::SNIPPET),
15322        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15323        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15324            kind: lsp::MarkupKind::Markdown,
15325            value: "```rust\nSome(2)\n```".to_string(),
15326        })),
15327        deprecated: Some(false),
15328        sort_text: Some("Some".to_string()),
15329        filter_text: Some("Some".to_string()),
15330        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15331        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15332            range: lsp::Range {
15333                start: lsp::Position {
15334                    line: 0,
15335                    character: 22,
15336                },
15337                end: lsp::Position {
15338                    line: 0,
15339                    character: 22,
15340                },
15341            },
15342            new_text: "Some(2)".to_string(),
15343        })),
15344        additional_text_edits: Some(vec![lsp::TextEdit {
15345            range: lsp::Range {
15346                start: lsp::Position {
15347                    line: 0,
15348                    character: 20,
15349                },
15350                end: lsp::Position {
15351                    line: 0,
15352                    character: 22,
15353                },
15354            },
15355            new_text: "".to_string(),
15356        }]),
15357        ..Default::default()
15358    };
15359
15360    let closure_completion_item = completion_item.clone();
15361    let counter = Arc::new(AtomicUsize::new(0));
15362    let counter_clone = counter.clone();
15363    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15364        let task_completion_item = closure_completion_item.clone();
15365        counter_clone.fetch_add(1, atomic::Ordering::Release);
15366        async move {
15367            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15368                is_incomplete: true,
15369                item_defaults: None,
15370                items: vec![task_completion_item],
15371            })))
15372        }
15373    });
15374
15375    cx.condition(|editor, _| editor.context_menu_visible())
15376        .await;
15377    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15378    assert!(request.next().await.is_some());
15379    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15380
15381    cx.simulate_keystrokes("S o m");
15382    cx.condition(|editor, _| editor.context_menu_visible())
15383        .await;
15384    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15385    assert!(request.next().await.is_some());
15386    assert!(request.next().await.is_some());
15387    assert!(request.next().await.is_some());
15388    request.close();
15389    assert!(request.next().await.is_none());
15390    assert_eq!(
15391        counter.load(atomic::Ordering::Acquire),
15392        4,
15393        "With the completions menu open, only one LSP request should happen per input"
15394    );
15395}
15396
15397#[gpui::test]
15398async fn test_toggle_comment(cx: &mut TestAppContext) {
15399    init_test(cx, |_| {});
15400    let mut cx = EditorTestContext::new(cx).await;
15401    let language = Arc::new(Language::new(
15402        LanguageConfig {
15403            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15404            ..Default::default()
15405        },
15406        Some(tree_sitter_rust::LANGUAGE.into()),
15407    ));
15408    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15409
15410    // If multiple selections intersect a line, the line is only toggled once.
15411    cx.set_state(indoc! {"
15412        fn a() {
15413            «//b();
15414            ˇ»// «c();
15415            //ˇ»  d();
15416        }
15417    "});
15418
15419    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15420
15421    cx.assert_editor_state(indoc! {"
15422        fn a() {
15423            «b();
15424            c();
15425            ˇ» d();
15426        }
15427    "});
15428
15429    // The comment prefix is inserted at the same column for every line in a
15430    // selection.
15431    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15432
15433    cx.assert_editor_state(indoc! {"
15434        fn a() {
15435            // «b();
15436            // c();
15437            ˇ»//  d();
15438        }
15439    "});
15440
15441    // If a selection ends at the beginning of a line, that line is not toggled.
15442    cx.set_selections_state(indoc! {"
15443        fn a() {
15444            // b();
15445            «// c();
15446        ˇ»    //  d();
15447        }
15448    "});
15449
15450    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15451
15452    cx.assert_editor_state(indoc! {"
15453        fn a() {
15454            // b();
15455            «c();
15456        ˇ»    //  d();
15457        }
15458    "});
15459
15460    // If a selection span a single line and is empty, the line is toggled.
15461    cx.set_state(indoc! {"
15462        fn a() {
15463            a();
15464            b();
15465        ˇ
15466        }
15467    "});
15468
15469    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15470
15471    cx.assert_editor_state(indoc! {"
15472        fn a() {
15473            a();
15474            b();
15475        //•ˇ
15476        }
15477    "});
15478
15479    // If a selection span multiple lines, empty lines are not toggled.
15480    cx.set_state(indoc! {"
15481        fn a() {
15482            «a();
15483
15484            c();ˇ»
15485        }
15486    "});
15487
15488    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15489
15490    cx.assert_editor_state(indoc! {"
15491        fn a() {
15492            // «a();
15493
15494            // c();ˇ»
15495        }
15496    "});
15497
15498    // If a selection includes multiple comment prefixes, all lines are uncommented.
15499    cx.set_state(indoc! {"
15500        fn a() {
15501            «// a();
15502            /// b();
15503            //! c();ˇ»
15504        }
15505    "});
15506
15507    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15508
15509    cx.assert_editor_state(indoc! {"
15510        fn a() {
15511            «a();
15512            b();
15513            c();ˇ»
15514        }
15515    "});
15516}
15517
15518#[gpui::test]
15519async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15520    init_test(cx, |_| {});
15521    let mut cx = EditorTestContext::new(cx).await;
15522    let language = Arc::new(Language::new(
15523        LanguageConfig {
15524            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15525            ..Default::default()
15526        },
15527        Some(tree_sitter_rust::LANGUAGE.into()),
15528    ));
15529    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15530
15531    let toggle_comments = &ToggleComments {
15532        advance_downwards: false,
15533        ignore_indent: true,
15534    };
15535
15536    // If multiple selections intersect a line, the line is only toggled once.
15537    cx.set_state(indoc! {"
15538        fn a() {
15539        //    «b();
15540        //    c();
15541        //    ˇ» d();
15542        }
15543    "});
15544
15545    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15546
15547    cx.assert_editor_state(indoc! {"
15548        fn a() {
15549            «b();
15550            c();
15551            ˇ» d();
15552        }
15553    "});
15554
15555    // The comment prefix is inserted at the beginning of each line
15556    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15557
15558    cx.assert_editor_state(indoc! {"
15559        fn a() {
15560        //    «b();
15561        //    c();
15562        //    ˇ» d();
15563        }
15564    "});
15565
15566    // If a selection ends at the beginning of a line, that line is not toggled.
15567    cx.set_selections_state(indoc! {"
15568        fn a() {
15569        //    b();
15570        //    «c();
15571        ˇ»//     d();
15572        }
15573    "});
15574
15575    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15576
15577    cx.assert_editor_state(indoc! {"
15578        fn a() {
15579        //    b();
15580            «c();
15581        ˇ»//     d();
15582        }
15583    "});
15584
15585    // If a selection span a single line and is empty, the line is toggled.
15586    cx.set_state(indoc! {"
15587        fn a() {
15588            a();
15589            b();
15590        ˇ
15591        }
15592    "});
15593
15594    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15595
15596    cx.assert_editor_state(indoc! {"
15597        fn a() {
15598            a();
15599            b();
15600        //ˇ
15601        }
15602    "});
15603
15604    // If a selection span multiple lines, empty lines are not toggled.
15605    cx.set_state(indoc! {"
15606        fn a() {
15607            «a();
15608
15609            c();ˇ»
15610        }
15611    "});
15612
15613    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15614
15615    cx.assert_editor_state(indoc! {"
15616        fn a() {
15617        //    «a();
15618
15619        //    c();ˇ»
15620        }
15621    "});
15622
15623    // If a selection includes multiple comment prefixes, all lines are uncommented.
15624    cx.set_state(indoc! {"
15625        fn a() {
15626        //    «a();
15627        ///    b();
15628        //!    c();ˇ»
15629        }
15630    "});
15631
15632    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15633
15634    cx.assert_editor_state(indoc! {"
15635        fn a() {
15636            «a();
15637            b();
15638            c();ˇ»
15639        }
15640    "});
15641}
15642
15643#[gpui::test]
15644async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15645    init_test(cx, |_| {});
15646
15647    let language = Arc::new(Language::new(
15648        LanguageConfig {
15649            line_comments: vec!["// ".into()],
15650            ..Default::default()
15651        },
15652        Some(tree_sitter_rust::LANGUAGE.into()),
15653    ));
15654
15655    let mut cx = EditorTestContext::new(cx).await;
15656
15657    cx.language_registry().add(language.clone());
15658    cx.update_buffer(|buffer, cx| {
15659        buffer.set_language(Some(language), cx);
15660    });
15661
15662    let toggle_comments = &ToggleComments {
15663        advance_downwards: true,
15664        ignore_indent: false,
15665    };
15666
15667    // Single cursor on one line -> advance
15668    // Cursor moves horizontally 3 characters as well on non-blank line
15669    cx.set_state(indoc!(
15670        "fn a() {
15671             ˇdog();
15672             cat();
15673        }"
15674    ));
15675    cx.update_editor(|editor, window, cx| {
15676        editor.toggle_comments(toggle_comments, window, cx);
15677    });
15678    cx.assert_editor_state(indoc!(
15679        "fn a() {
15680             // dog();
15681             catˇ();
15682        }"
15683    ));
15684
15685    // Single selection on one line -> don't advance
15686    cx.set_state(indoc!(
15687        "fn a() {
15688             «dog()ˇ»;
15689             cat();
15690        }"
15691    ));
15692    cx.update_editor(|editor, window, cx| {
15693        editor.toggle_comments(toggle_comments, window, cx);
15694    });
15695    cx.assert_editor_state(indoc!(
15696        "fn a() {
15697             // «dog()ˇ»;
15698             cat();
15699        }"
15700    ));
15701
15702    // Multiple cursors on one line -> advance
15703    cx.set_state(indoc!(
15704        "fn a() {
15705             ˇdˇog();
15706             cat();
15707        }"
15708    ));
15709    cx.update_editor(|editor, window, cx| {
15710        editor.toggle_comments(toggle_comments, window, cx);
15711    });
15712    cx.assert_editor_state(indoc!(
15713        "fn a() {
15714             // dog();
15715             catˇ(ˇ);
15716        }"
15717    ));
15718
15719    // Multiple cursors on one line, with selection -> don't advance
15720    cx.set_state(indoc!(
15721        "fn a() {
15722             ˇdˇog«()ˇ»;
15723             cat();
15724        }"
15725    ));
15726    cx.update_editor(|editor, window, cx| {
15727        editor.toggle_comments(toggle_comments, window, cx);
15728    });
15729    cx.assert_editor_state(indoc!(
15730        "fn a() {
15731             // ˇdˇog«()ˇ»;
15732             cat();
15733        }"
15734    ));
15735
15736    // Single cursor on one line -> advance
15737    // Cursor moves to column 0 on blank line
15738    cx.set_state(indoc!(
15739        "fn a() {
15740             ˇdog();
15741
15742             cat();
15743        }"
15744    ));
15745    cx.update_editor(|editor, window, cx| {
15746        editor.toggle_comments(toggle_comments, window, cx);
15747    });
15748    cx.assert_editor_state(indoc!(
15749        "fn a() {
15750             // dog();
15751        ˇ
15752             cat();
15753        }"
15754    ));
15755
15756    // Single cursor on one line -> advance
15757    // Cursor starts and ends at column 0
15758    cx.set_state(indoc!(
15759        "fn a() {
15760         ˇ    dog();
15761             cat();
15762        }"
15763    ));
15764    cx.update_editor(|editor, window, cx| {
15765        editor.toggle_comments(toggle_comments, window, cx);
15766    });
15767    cx.assert_editor_state(indoc!(
15768        "fn a() {
15769             // dog();
15770         ˇ    cat();
15771        }"
15772    ));
15773}
15774
15775#[gpui::test]
15776async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15777    init_test(cx, |_| {});
15778
15779    let mut cx = EditorTestContext::new(cx).await;
15780
15781    let html_language = Arc::new(
15782        Language::new(
15783            LanguageConfig {
15784                name: "HTML".into(),
15785                block_comment: Some(BlockCommentConfig {
15786                    start: "<!-- ".into(),
15787                    prefix: "".into(),
15788                    end: " -->".into(),
15789                    tab_size: 0,
15790                }),
15791                ..Default::default()
15792            },
15793            Some(tree_sitter_html::LANGUAGE.into()),
15794        )
15795        .with_injection_query(
15796            r#"
15797            (script_element
15798                (raw_text) @injection.content
15799                (#set! injection.language "javascript"))
15800            "#,
15801        )
15802        .unwrap(),
15803    );
15804
15805    let javascript_language = Arc::new(Language::new(
15806        LanguageConfig {
15807            name: "JavaScript".into(),
15808            line_comments: vec!["// ".into()],
15809            ..Default::default()
15810        },
15811        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15812    ));
15813
15814    cx.language_registry().add(html_language.clone());
15815    cx.language_registry().add(javascript_language);
15816    cx.update_buffer(|buffer, cx| {
15817        buffer.set_language(Some(html_language), cx);
15818    });
15819
15820    // Toggle comments for empty selections
15821    cx.set_state(
15822        &r#"
15823            <p>A</p>ˇ
15824            <p>B</p>ˇ
15825            <p>C</p>ˇ
15826        "#
15827        .unindent(),
15828    );
15829    cx.update_editor(|editor, window, cx| {
15830        editor.toggle_comments(&ToggleComments::default(), window, cx)
15831    });
15832    cx.assert_editor_state(
15833        &r#"
15834            <!-- <p>A</p>ˇ -->
15835            <!-- <p>B</p>ˇ -->
15836            <!-- <p>C</p>ˇ -->
15837        "#
15838        .unindent(),
15839    );
15840    cx.update_editor(|editor, window, cx| {
15841        editor.toggle_comments(&ToggleComments::default(), window, cx)
15842    });
15843    cx.assert_editor_state(
15844        &r#"
15845            <p>A</p>ˇ
15846            <p>B</p>ˇ
15847            <p>C</p>ˇ
15848        "#
15849        .unindent(),
15850    );
15851
15852    // Toggle comments for mixture of empty and non-empty selections, where
15853    // multiple selections occupy a given line.
15854    cx.set_state(
15855        &r#"
15856            <p>A«</p>
15857            <p>ˇ»B</p>ˇ
15858            <p>C«</p>
15859            <p>ˇ»D</p>ˇ
15860        "#
15861        .unindent(),
15862    );
15863
15864    cx.update_editor(|editor, window, cx| {
15865        editor.toggle_comments(&ToggleComments::default(), window, cx)
15866    });
15867    cx.assert_editor_state(
15868        &r#"
15869            <!-- <p>A«</p>
15870            <p>ˇ»B</p>ˇ -->
15871            <!-- <p>C«</p>
15872            <p>ˇ»D</p>ˇ -->
15873        "#
15874        .unindent(),
15875    );
15876    cx.update_editor(|editor, window, cx| {
15877        editor.toggle_comments(&ToggleComments::default(), window, cx)
15878    });
15879    cx.assert_editor_state(
15880        &r#"
15881            <p>A«</p>
15882            <p>ˇ»B</p>ˇ
15883            <p>C«</p>
15884            <p>ˇ»D</p>ˇ
15885        "#
15886        .unindent(),
15887    );
15888
15889    // Toggle comments when different languages are active for different
15890    // selections.
15891    cx.set_state(
15892        &r#"
15893            ˇ<script>
15894                ˇvar x = new Y();
15895            ˇ</script>
15896        "#
15897        .unindent(),
15898    );
15899    cx.executor().run_until_parked();
15900    cx.update_editor(|editor, window, cx| {
15901        editor.toggle_comments(&ToggleComments::default(), window, cx)
15902    });
15903    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15904    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15905    cx.assert_editor_state(
15906        &r#"
15907            <!-- ˇ<script> -->
15908                // ˇvar x = new Y();
15909            <!-- ˇ</script> -->
15910        "#
15911        .unindent(),
15912    );
15913}
15914
15915#[gpui::test]
15916fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15917    init_test(cx, |_| {});
15918
15919    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15920    let multibuffer = cx.new(|cx| {
15921        let mut multibuffer = MultiBuffer::new(ReadWrite);
15922        multibuffer.push_excerpts(
15923            buffer.clone(),
15924            [
15925                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15926                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15927            ],
15928            cx,
15929        );
15930        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15931        multibuffer
15932    });
15933
15934    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15935    editor.update_in(cx, |editor, window, cx| {
15936        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15937        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15938            s.select_ranges([
15939                Point::new(0, 0)..Point::new(0, 0),
15940                Point::new(1, 0)..Point::new(1, 0),
15941            ])
15942        });
15943
15944        editor.handle_input("X", window, cx);
15945        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15946        assert_eq!(
15947            editor.selections.ranges(cx),
15948            [
15949                Point::new(0, 1)..Point::new(0, 1),
15950                Point::new(1, 1)..Point::new(1, 1),
15951            ]
15952        );
15953
15954        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15955        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15956            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15957        });
15958        editor.backspace(&Default::default(), window, cx);
15959        assert_eq!(editor.text(cx), "Xa\nbbb");
15960        assert_eq!(
15961            editor.selections.ranges(cx),
15962            [Point::new(1, 0)..Point::new(1, 0)]
15963        );
15964
15965        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15966            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15967        });
15968        editor.backspace(&Default::default(), window, cx);
15969        assert_eq!(editor.text(cx), "X\nbb");
15970        assert_eq!(
15971            editor.selections.ranges(cx),
15972            [Point::new(0, 1)..Point::new(0, 1)]
15973        );
15974    });
15975}
15976
15977#[gpui::test]
15978fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15979    init_test(cx, |_| {});
15980
15981    let markers = vec![('[', ']').into(), ('(', ')').into()];
15982    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15983        indoc! {"
15984            [aaaa
15985            (bbbb]
15986            cccc)",
15987        },
15988        markers.clone(),
15989    );
15990    let excerpt_ranges = markers.into_iter().map(|marker| {
15991        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15992        ExcerptRange::new(context)
15993    });
15994    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15995    let multibuffer = cx.new(|cx| {
15996        let mut multibuffer = MultiBuffer::new(ReadWrite);
15997        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15998        multibuffer
15999    });
16000
16001    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16002    editor.update_in(cx, |editor, window, cx| {
16003        let (expected_text, selection_ranges) = marked_text_ranges(
16004            indoc! {"
16005                aaaa
16006                bˇbbb
16007                bˇbbˇb
16008                cccc"
16009            },
16010            true,
16011        );
16012        assert_eq!(editor.text(cx), expected_text);
16013        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16014            s.select_ranges(selection_ranges)
16015        });
16016
16017        editor.handle_input("X", window, cx);
16018
16019        let (expected_text, expected_selections) = marked_text_ranges(
16020            indoc! {"
16021                aaaa
16022                bXˇbbXb
16023                bXˇbbXˇb
16024                cccc"
16025            },
16026            false,
16027        );
16028        assert_eq!(editor.text(cx), expected_text);
16029        assert_eq!(editor.selections.ranges(cx), expected_selections);
16030
16031        editor.newline(&Newline, window, cx);
16032        let (expected_text, expected_selections) = marked_text_ranges(
16033            indoc! {"
16034                aaaa
16035                bX
16036                ˇbbX
16037                b
16038                bX
16039                ˇbbX
16040                ˇb
16041                cccc"
16042            },
16043            false,
16044        );
16045        assert_eq!(editor.text(cx), expected_text);
16046        assert_eq!(editor.selections.ranges(cx), expected_selections);
16047    });
16048}
16049
16050#[gpui::test]
16051fn test_refresh_selections(cx: &mut TestAppContext) {
16052    init_test(cx, |_| {});
16053
16054    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16055    let mut excerpt1_id = None;
16056    let multibuffer = cx.new(|cx| {
16057        let mut multibuffer = MultiBuffer::new(ReadWrite);
16058        excerpt1_id = multibuffer
16059            .push_excerpts(
16060                buffer.clone(),
16061                [
16062                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16063                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16064                ],
16065                cx,
16066            )
16067            .into_iter()
16068            .next();
16069        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16070        multibuffer
16071    });
16072
16073    let editor = cx.add_window(|window, cx| {
16074        let mut editor = build_editor(multibuffer.clone(), window, cx);
16075        let snapshot = editor.snapshot(window, cx);
16076        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16077            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16078        });
16079        editor.begin_selection(
16080            Point::new(2, 1).to_display_point(&snapshot),
16081            true,
16082            1,
16083            window,
16084            cx,
16085        );
16086        assert_eq!(
16087            editor.selections.ranges(cx),
16088            [
16089                Point::new(1, 3)..Point::new(1, 3),
16090                Point::new(2, 1)..Point::new(2, 1),
16091            ]
16092        );
16093        editor
16094    });
16095
16096    // Refreshing selections is a no-op when excerpts haven't changed.
16097    _ = editor.update(cx, |editor, window, cx| {
16098        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16099        assert_eq!(
16100            editor.selections.ranges(cx),
16101            [
16102                Point::new(1, 3)..Point::new(1, 3),
16103                Point::new(2, 1)..Point::new(2, 1),
16104            ]
16105        );
16106    });
16107
16108    multibuffer.update(cx, |multibuffer, cx| {
16109        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16110    });
16111    _ = editor.update(cx, |editor, window, cx| {
16112        // Removing an excerpt causes the first selection to become degenerate.
16113        assert_eq!(
16114            editor.selections.ranges(cx),
16115            [
16116                Point::new(0, 0)..Point::new(0, 0),
16117                Point::new(0, 1)..Point::new(0, 1)
16118            ]
16119        );
16120
16121        // Refreshing selections will relocate the first selection to the original buffer
16122        // location.
16123        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16124        assert_eq!(
16125            editor.selections.ranges(cx),
16126            [
16127                Point::new(0, 1)..Point::new(0, 1),
16128                Point::new(0, 3)..Point::new(0, 3)
16129            ]
16130        );
16131        assert!(editor.selections.pending_anchor().is_some());
16132    });
16133}
16134
16135#[gpui::test]
16136fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16137    init_test(cx, |_| {});
16138
16139    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16140    let mut excerpt1_id = None;
16141    let multibuffer = cx.new(|cx| {
16142        let mut multibuffer = MultiBuffer::new(ReadWrite);
16143        excerpt1_id = multibuffer
16144            .push_excerpts(
16145                buffer.clone(),
16146                [
16147                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16148                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16149                ],
16150                cx,
16151            )
16152            .into_iter()
16153            .next();
16154        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16155        multibuffer
16156    });
16157
16158    let editor = cx.add_window(|window, cx| {
16159        let mut editor = build_editor(multibuffer.clone(), window, cx);
16160        let snapshot = editor.snapshot(window, cx);
16161        editor.begin_selection(
16162            Point::new(1, 3).to_display_point(&snapshot),
16163            false,
16164            1,
16165            window,
16166            cx,
16167        );
16168        assert_eq!(
16169            editor.selections.ranges(cx),
16170            [Point::new(1, 3)..Point::new(1, 3)]
16171        );
16172        editor
16173    });
16174
16175    multibuffer.update(cx, |multibuffer, cx| {
16176        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16177    });
16178    _ = editor.update(cx, |editor, window, cx| {
16179        assert_eq!(
16180            editor.selections.ranges(cx),
16181            [Point::new(0, 0)..Point::new(0, 0)]
16182        );
16183
16184        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16185        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16186        assert_eq!(
16187            editor.selections.ranges(cx),
16188            [Point::new(0, 3)..Point::new(0, 3)]
16189        );
16190        assert!(editor.selections.pending_anchor().is_some());
16191    });
16192}
16193
16194#[gpui::test]
16195async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16196    init_test(cx, |_| {});
16197
16198    let language = Arc::new(
16199        Language::new(
16200            LanguageConfig {
16201                brackets: BracketPairConfig {
16202                    pairs: vec![
16203                        BracketPair {
16204                            start: "{".to_string(),
16205                            end: "}".to_string(),
16206                            close: true,
16207                            surround: true,
16208                            newline: true,
16209                        },
16210                        BracketPair {
16211                            start: "/* ".to_string(),
16212                            end: " */".to_string(),
16213                            close: true,
16214                            surround: true,
16215                            newline: true,
16216                        },
16217                    ],
16218                    ..Default::default()
16219                },
16220                ..Default::default()
16221            },
16222            Some(tree_sitter_rust::LANGUAGE.into()),
16223        )
16224        .with_indents_query("")
16225        .unwrap(),
16226    );
16227
16228    let text = concat!(
16229        "{   }\n",     //
16230        "  x\n",       //
16231        "  /*   */\n", //
16232        "x\n",         //
16233        "{{} }\n",     //
16234    );
16235
16236    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16237    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16238    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16239    editor
16240        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16241        .await;
16242
16243    editor.update_in(cx, |editor, window, cx| {
16244        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16245            s.select_display_ranges([
16246                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16247                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16248                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16249            ])
16250        });
16251        editor.newline(&Newline, window, cx);
16252
16253        assert_eq!(
16254            editor.buffer().read(cx).read(cx).text(),
16255            concat!(
16256                "{ \n",    // Suppress rustfmt
16257                "\n",      //
16258                "}\n",     //
16259                "  x\n",   //
16260                "  /* \n", //
16261                "  \n",    //
16262                "  */\n",  //
16263                "x\n",     //
16264                "{{} \n",  //
16265                "}\n",     //
16266            )
16267        );
16268    });
16269}
16270
16271#[gpui::test]
16272fn test_highlighted_ranges(cx: &mut TestAppContext) {
16273    init_test(cx, |_| {});
16274
16275    let editor = cx.add_window(|window, cx| {
16276        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16277        build_editor(buffer, window, cx)
16278    });
16279
16280    _ = editor.update(cx, |editor, window, cx| {
16281        struct Type1;
16282        struct Type2;
16283
16284        let buffer = editor.buffer.read(cx).snapshot(cx);
16285
16286        let anchor_range =
16287            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16288
16289        editor.highlight_background::<Type1>(
16290            &[
16291                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16292                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16293                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16294                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16295            ],
16296            |_| Hsla::red(),
16297            cx,
16298        );
16299        editor.highlight_background::<Type2>(
16300            &[
16301                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16302                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16303                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16304                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16305            ],
16306            |_| Hsla::green(),
16307            cx,
16308        );
16309
16310        let snapshot = editor.snapshot(window, cx);
16311        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16312            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16313            &snapshot,
16314            cx.theme(),
16315        );
16316        assert_eq!(
16317            highlighted_ranges,
16318            &[
16319                (
16320                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16321                    Hsla::green(),
16322                ),
16323                (
16324                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16325                    Hsla::red(),
16326                ),
16327                (
16328                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16329                    Hsla::green(),
16330                ),
16331                (
16332                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16333                    Hsla::red(),
16334                ),
16335            ]
16336        );
16337        assert_eq!(
16338            editor.sorted_background_highlights_in_range(
16339                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16340                &snapshot,
16341                cx.theme(),
16342            ),
16343            &[(
16344                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16345                Hsla::red(),
16346            )]
16347        );
16348    });
16349}
16350
16351#[gpui::test]
16352async fn test_following(cx: &mut TestAppContext) {
16353    init_test(cx, |_| {});
16354
16355    let fs = FakeFs::new(cx.executor());
16356    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16357
16358    let buffer = project.update(cx, |project, cx| {
16359        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16360        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16361    });
16362    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16363    let follower = cx.update(|cx| {
16364        cx.open_window(
16365            WindowOptions {
16366                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16367                    gpui::Point::new(px(0.), px(0.)),
16368                    gpui::Point::new(px(10.), px(80.)),
16369                ))),
16370                ..Default::default()
16371            },
16372            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16373        )
16374        .unwrap()
16375    });
16376
16377    let is_still_following = Rc::new(RefCell::new(true));
16378    let follower_edit_event_count = Rc::new(RefCell::new(0));
16379    let pending_update = Rc::new(RefCell::new(None));
16380    let leader_entity = leader.root(cx).unwrap();
16381    let follower_entity = follower.root(cx).unwrap();
16382    _ = follower.update(cx, {
16383        let update = pending_update.clone();
16384        let is_still_following = is_still_following.clone();
16385        let follower_edit_event_count = follower_edit_event_count.clone();
16386        |_, window, cx| {
16387            cx.subscribe_in(
16388                &leader_entity,
16389                window,
16390                move |_, leader, event, window, cx| {
16391                    leader.read(cx).add_event_to_update_proto(
16392                        event,
16393                        &mut update.borrow_mut(),
16394                        window,
16395                        cx,
16396                    );
16397                },
16398            )
16399            .detach();
16400
16401            cx.subscribe_in(
16402                &follower_entity,
16403                window,
16404                move |_, _, event: &EditorEvent, _window, _cx| {
16405                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16406                        *is_still_following.borrow_mut() = false;
16407                    }
16408
16409                    if let EditorEvent::BufferEdited = event {
16410                        *follower_edit_event_count.borrow_mut() += 1;
16411                    }
16412                },
16413            )
16414            .detach();
16415        }
16416    });
16417
16418    // Update the selections only
16419    _ = leader.update(cx, |leader, window, cx| {
16420        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16421            s.select_ranges([1..1])
16422        });
16423    });
16424    follower
16425        .update(cx, |follower, window, cx| {
16426            follower.apply_update_proto(
16427                &project,
16428                pending_update.borrow_mut().take().unwrap(),
16429                window,
16430                cx,
16431            )
16432        })
16433        .unwrap()
16434        .await
16435        .unwrap();
16436    _ = follower.update(cx, |follower, _, cx| {
16437        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
16438    });
16439    assert!(*is_still_following.borrow());
16440    assert_eq!(*follower_edit_event_count.borrow(), 0);
16441
16442    // Update the scroll position only
16443    _ = leader.update(cx, |leader, window, cx| {
16444        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16445    });
16446    follower
16447        .update(cx, |follower, window, cx| {
16448            follower.apply_update_proto(
16449                &project,
16450                pending_update.borrow_mut().take().unwrap(),
16451                window,
16452                cx,
16453            )
16454        })
16455        .unwrap()
16456        .await
16457        .unwrap();
16458    assert_eq!(
16459        follower
16460            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16461            .unwrap(),
16462        gpui::Point::new(1.5, 3.5)
16463    );
16464    assert!(*is_still_following.borrow());
16465    assert_eq!(*follower_edit_event_count.borrow(), 0);
16466
16467    // Update the selections and scroll position. The follower's scroll position is updated
16468    // via autoscroll, not via the leader's exact scroll position.
16469    _ = leader.update(cx, |leader, window, cx| {
16470        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16471            s.select_ranges([0..0])
16472        });
16473        leader.request_autoscroll(Autoscroll::newest(), cx);
16474        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16475    });
16476    follower
16477        .update(cx, |follower, window, cx| {
16478            follower.apply_update_proto(
16479                &project,
16480                pending_update.borrow_mut().take().unwrap(),
16481                window,
16482                cx,
16483            )
16484        })
16485        .unwrap()
16486        .await
16487        .unwrap();
16488    _ = follower.update(cx, |follower, _, cx| {
16489        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16490        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16491    });
16492    assert!(*is_still_following.borrow());
16493
16494    // Creating a pending selection that precedes another selection
16495    _ = leader.update(cx, |leader, window, cx| {
16496        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16497            s.select_ranges([1..1])
16498        });
16499        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16500    });
16501    follower
16502        .update(cx, |follower, window, cx| {
16503            follower.apply_update_proto(
16504                &project,
16505                pending_update.borrow_mut().take().unwrap(),
16506                window,
16507                cx,
16508            )
16509        })
16510        .unwrap()
16511        .await
16512        .unwrap();
16513    _ = follower.update(cx, |follower, _, cx| {
16514        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16515    });
16516    assert!(*is_still_following.borrow());
16517
16518    // Extend the pending selection so that it surrounds another selection
16519    _ = leader.update(cx, |leader, window, cx| {
16520        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16521    });
16522    follower
16523        .update(cx, |follower, window, cx| {
16524            follower.apply_update_proto(
16525                &project,
16526                pending_update.borrow_mut().take().unwrap(),
16527                window,
16528                cx,
16529            )
16530        })
16531        .unwrap()
16532        .await
16533        .unwrap();
16534    _ = follower.update(cx, |follower, _, cx| {
16535        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16536    });
16537
16538    // Scrolling locally breaks the follow
16539    _ = follower.update(cx, |follower, window, cx| {
16540        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16541        follower.set_scroll_anchor(
16542            ScrollAnchor {
16543                anchor: top_anchor,
16544                offset: gpui::Point::new(0.0, 0.5),
16545            },
16546            window,
16547            cx,
16548        );
16549    });
16550    assert!(!(*is_still_following.borrow()));
16551}
16552
16553#[gpui::test]
16554async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16555    init_test(cx, |_| {});
16556
16557    let fs = FakeFs::new(cx.executor());
16558    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16559    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16560    let pane = workspace
16561        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16562        .unwrap();
16563
16564    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16565
16566    let leader = pane.update_in(cx, |_, window, cx| {
16567        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16568        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16569    });
16570
16571    // Start following the editor when it has no excerpts.
16572    let mut state_message =
16573        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16574    let workspace_entity = workspace.root(cx).unwrap();
16575    let follower_1 = cx
16576        .update_window(*workspace.deref(), |_, window, cx| {
16577            Editor::from_state_proto(
16578                workspace_entity,
16579                ViewId {
16580                    creator: CollaboratorId::PeerId(PeerId::default()),
16581                    id: 0,
16582                },
16583                &mut state_message,
16584                window,
16585                cx,
16586            )
16587        })
16588        .unwrap()
16589        .unwrap()
16590        .await
16591        .unwrap();
16592
16593    let update_message = Rc::new(RefCell::new(None));
16594    follower_1.update_in(cx, {
16595        let update = update_message.clone();
16596        |_, window, cx| {
16597            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16598                leader.read(cx).add_event_to_update_proto(
16599                    event,
16600                    &mut update.borrow_mut(),
16601                    window,
16602                    cx,
16603                );
16604            })
16605            .detach();
16606        }
16607    });
16608
16609    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16610        (
16611            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16612            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16613        )
16614    });
16615
16616    // Insert some excerpts.
16617    leader.update(cx, |leader, cx| {
16618        leader.buffer.update(cx, |multibuffer, cx| {
16619            multibuffer.set_excerpts_for_path(
16620                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
16621                buffer_1.clone(),
16622                vec![
16623                    Point::row_range(0..3),
16624                    Point::row_range(1..6),
16625                    Point::row_range(12..15),
16626                ],
16627                0,
16628                cx,
16629            );
16630            multibuffer.set_excerpts_for_path(
16631                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
16632                buffer_2.clone(),
16633                vec![Point::row_range(0..6), Point::row_range(8..12)],
16634                0,
16635                cx,
16636            );
16637        });
16638    });
16639
16640    // Apply the update of adding the excerpts.
16641    follower_1
16642        .update_in(cx, |follower, window, cx| {
16643            follower.apply_update_proto(
16644                &project,
16645                update_message.borrow().clone().unwrap(),
16646                window,
16647                cx,
16648            )
16649        })
16650        .await
16651        .unwrap();
16652    assert_eq!(
16653        follower_1.update(cx, |editor, cx| editor.text(cx)),
16654        leader.update(cx, |editor, cx| editor.text(cx))
16655    );
16656    update_message.borrow_mut().take();
16657
16658    // Start following separately after it already has excerpts.
16659    let mut state_message =
16660        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16661    let workspace_entity = workspace.root(cx).unwrap();
16662    let follower_2 = cx
16663        .update_window(*workspace.deref(), |_, window, cx| {
16664            Editor::from_state_proto(
16665                workspace_entity,
16666                ViewId {
16667                    creator: CollaboratorId::PeerId(PeerId::default()),
16668                    id: 0,
16669                },
16670                &mut state_message,
16671                window,
16672                cx,
16673            )
16674        })
16675        .unwrap()
16676        .unwrap()
16677        .await
16678        .unwrap();
16679    assert_eq!(
16680        follower_2.update(cx, |editor, cx| editor.text(cx)),
16681        leader.update(cx, |editor, cx| editor.text(cx))
16682    );
16683
16684    // Remove some excerpts.
16685    leader.update(cx, |leader, cx| {
16686        leader.buffer.update(cx, |multibuffer, cx| {
16687            let excerpt_ids = multibuffer.excerpt_ids();
16688            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16689            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16690        });
16691    });
16692
16693    // Apply the update of removing the excerpts.
16694    follower_1
16695        .update_in(cx, |follower, window, cx| {
16696            follower.apply_update_proto(
16697                &project,
16698                update_message.borrow().clone().unwrap(),
16699                window,
16700                cx,
16701            )
16702        })
16703        .await
16704        .unwrap();
16705    follower_2
16706        .update_in(cx, |follower, window, cx| {
16707            follower.apply_update_proto(
16708                &project,
16709                update_message.borrow().clone().unwrap(),
16710                window,
16711                cx,
16712            )
16713        })
16714        .await
16715        .unwrap();
16716    update_message.borrow_mut().take();
16717    assert_eq!(
16718        follower_1.update(cx, |editor, cx| editor.text(cx)),
16719        leader.update(cx, |editor, cx| editor.text(cx))
16720    );
16721}
16722
16723#[gpui::test]
16724async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16725    init_test(cx, |_| {});
16726
16727    let mut cx = EditorTestContext::new(cx).await;
16728    let lsp_store =
16729        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16730
16731    cx.set_state(indoc! {"
16732        ˇfn func(abc def: i32) -> u32 {
16733        }
16734    "});
16735
16736    cx.update(|_, cx| {
16737        lsp_store.update(cx, |lsp_store, cx| {
16738            lsp_store
16739                .update_diagnostics(
16740                    LanguageServerId(0),
16741                    lsp::PublishDiagnosticsParams {
16742                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16743                        version: None,
16744                        diagnostics: vec![
16745                            lsp::Diagnostic {
16746                                range: lsp::Range::new(
16747                                    lsp::Position::new(0, 11),
16748                                    lsp::Position::new(0, 12),
16749                                ),
16750                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16751                                ..Default::default()
16752                            },
16753                            lsp::Diagnostic {
16754                                range: lsp::Range::new(
16755                                    lsp::Position::new(0, 12),
16756                                    lsp::Position::new(0, 15),
16757                                ),
16758                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16759                                ..Default::default()
16760                            },
16761                            lsp::Diagnostic {
16762                                range: lsp::Range::new(
16763                                    lsp::Position::new(0, 25),
16764                                    lsp::Position::new(0, 28),
16765                                ),
16766                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16767                                ..Default::default()
16768                            },
16769                        ],
16770                    },
16771                    None,
16772                    DiagnosticSourceKind::Pushed,
16773                    &[],
16774                    cx,
16775                )
16776                .unwrap()
16777        });
16778    });
16779
16780    executor.run_until_parked();
16781
16782    cx.update_editor(|editor, window, cx| {
16783        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16784    });
16785
16786    cx.assert_editor_state(indoc! {"
16787        fn func(abc def: i32) -> ˇu32 {
16788        }
16789    "});
16790
16791    cx.update_editor(|editor, window, cx| {
16792        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16793    });
16794
16795    cx.assert_editor_state(indoc! {"
16796        fn func(abc ˇdef: i32) -> u32 {
16797        }
16798    "});
16799
16800    cx.update_editor(|editor, window, cx| {
16801        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16802    });
16803
16804    cx.assert_editor_state(indoc! {"
16805        fn func(abcˇ def: i32) -> u32 {
16806        }
16807    "});
16808
16809    cx.update_editor(|editor, window, cx| {
16810        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16811    });
16812
16813    cx.assert_editor_state(indoc! {"
16814        fn func(abc def: i32) -> ˇu32 {
16815        }
16816    "});
16817}
16818
16819#[gpui::test]
16820async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16821    init_test(cx, |_| {});
16822
16823    let mut cx = EditorTestContext::new(cx).await;
16824
16825    let diff_base = r#"
16826        use some::mod;
16827
16828        const A: u32 = 42;
16829
16830        fn main() {
16831            println!("hello");
16832
16833            println!("world");
16834        }
16835        "#
16836    .unindent();
16837
16838    // Edits are modified, removed, modified, added
16839    cx.set_state(
16840        &r#"
16841        use some::modified;
16842
16843        ˇ
16844        fn main() {
16845            println!("hello there");
16846
16847            println!("around the");
16848            println!("world");
16849        }
16850        "#
16851        .unindent(),
16852    );
16853
16854    cx.set_head_text(&diff_base);
16855    executor.run_until_parked();
16856
16857    cx.update_editor(|editor, window, cx| {
16858        //Wrap around the bottom of the buffer
16859        for _ in 0..3 {
16860            editor.go_to_next_hunk(&GoToHunk, window, cx);
16861        }
16862    });
16863
16864    cx.assert_editor_state(
16865        &r#"
16866        ˇuse some::modified;
16867
16868
16869        fn main() {
16870            println!("hello there");
16871
16872            println!("around the");
16873            println!("world");
16874        }
16875        "#
16876        .unindent(),
16877    );
16878
16879    cx.update_editor(|editor, window, cx| {
16880        //Wrap around the top of the buffer
16881        for _ in 0..2 {
16882            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16883        }
16884    });
16885
16886    cx.assert_editor_state(
16887        &r#"
16888        use some::modified;
16889
16890
16891        fn main() {
16892        ˇ    println!("hello there");
16893
16894            println!("around the");
16895            println!("world");
16896        }
16897        "#
16898        .unindent(),
16899    );
16900
16901    cx.update_editor(|editor, window, cx| {
16902        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16903    });
16904
16905    cx.assert_editor_state(
16906        &r#"
16907        use some::modified;
16908
16909        ˇ
16910        fn main() {
16911            println!("hello there");
16912
16913            println!("around the");
16914            println!("world");
16915        }
16916        "#
16917        .unindent(),
16918    );
16919
16920    cx.update_editor(|editor, window, cx| {
16921        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16922    });
16923
16924    cx.assert_editor_state(
16925        &r#"
16926        ˇuse some::modified;
16927
16928
16929        fn main() {
16930            println!("hello there");
16931
16932            println!("around the");
16933            println!("world");
16934        }
16935        "#
16936        .unindent(),
16937    );
16938
16939    cx.update_editor(|editor, window, cx| {
16940        for _ in 0..2 {
16941            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16942        }
16943    });
16944
16945    cx.assert_editor_state(
16946        &r#"
16947        use some::modified;
16948
16949
16950        fn main() {
16951        ˇ    println!("hello there");
16952
16953            println!("around the");
16954            println!("world");
16955        }
16956        "#
16957        .unindent(),
16958    );
16959
16960    cx.update_editor(|editor, window, cx| {
16961        editor.fold(&Fold, window, cx);
16962    });
16963
16964    cx.update_editor(|editor, window, cx| {
16965        editor.go_to_next_hunk(&GoToHunk, window, cx);
16966    });
16967
16968    cx.assert_editor_state(
16969        &r#"
16970        ˇuse some::modified;
16971
16972
16973        fn main() {
16974            println!("hello there");
16975
16976            println!("around the");
16977            println!("world");
16978        }
16979        "#
16980        .unindent(),
16981    );
16982}
16983
16984#[test]
16985fn test_split_words() {
16986    fn split(text: &str) -> Vec<&str> {
16987        split_words(text).collect()
16988    }
16989
16990    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16991    assert_eq!(split("hello_world"), &["hello_", "world"]);
16992    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16993    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16994    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16995    assert_eq!(split("helloworld"), &["helloworld"]);
16996
16997    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16998}
16999
17000#[gpui::test]
17001async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17002    init_test(cx, |_| {});
17003
17004    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17005    let mut assert = |before, after| {
17006        let _state_context = cx.set_state(before);
17007        cx.run_until_parked();
17008        cx.update_editor(|editor, window, cx| {
17009            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17010        });
17011        cx.run_until_parked();
17012        cx.assert_editor_state(after);
17013    };
17014
17015    // Outside bracket jumps to outside of matching bracket
17016    assert("console.logˇ(var);", "console.log(var)ˇ;");
17017    assert("console.log(var)ˇ;", "console.logˇ(var);");
17018
17019    // Inside bracket jumps to inside of matching bracket
17020    assert("console.log(ˇvar);", "console.log(varˇ);");
17021    assert("console.log(varˇ);", "console.log(ˇvar);");
17022
17023    // When outside a bracket and inside, favor jumping to the inside bracket
17024    assert(
17025        "console.log('foo', [1, 2, 3]ˇ);",
17026        "console.log(ˇ'foo', [1, 2, 3]);",
17027    );
17028    assert(
17029        "console.log(ˇ'foo', [1, 2, 3]);",
17030        "console.log('foo', [1, 2, 3]ˇ);",
17031    );
17032
17033    // Bias forward if two options are equally likely
17034    assert(
17035        "let result = curried_fun()ˇ();",
17036        "let result = curried_fun()()ˇ;",
17037    );
17038
17039    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17040    assert(
17041        indoc! {"
17042            function test() {
17043                console.log('test')ˇ
17044            }"},
17045        indoc! {"
17046            function test() {
17047                console.logˇ('test')
17048            }"},
17049    );
17050}
17051
17052#[gpui::test]
17053async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17054    init_test(cx, |_| {});
17055
17056    let fs = FakeFs::new(cx.executor());
17057    fs.insert_tree(
17058        path!("/a"),
17059        json!({
17060            "main.rs": "fn main() { let a = 5; }",
17061            "other.rs": "// Test file",
17062        }),
17063    )
17064    .await;
17065    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17066
17067    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17068    language_registry.add(Arc::new(Language::new(
17069        LanguageConfig {
17070            name: "Rust".into(),
17071            matcher: LanguageMatcher {
17072                path_suffixes: vec!["rs".to_string()],
17073                ..Default::default()
17074            },
17075            brackets: BracketPairConfig {
17076                pairs: vec![BracketPair {
17077                    start: "{".to_string(),
17078                    end: "}".to_string(),
17079                    close: true,
17080                    surround: true,
17081                    newline: true,
17082                }],
17083                disabled_scopes_by_bracket_ix: Vec::new(),
17084            },
17085            ..Default::default()
17086        },
17087        Some(tree_sitter_rust::LANGUAGE.into()),
17088    )));
17089    let mut fake_servers = language_registry.register_fake_lsp(
17090        "Rust",
17091        FakeLspAdapter {
17092            capabilities: lsp::ServerCapabilities {
17093                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17094                    first_trigger_character: "{".to_string(),
17095                    more_trigger_character: None,
17096                }),
17097                ..Default::default()
17098            },
17099            ..Default::default()
17100        },
17101    );
17102
17103    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17104
17105    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17106
17107    let worktree_id = workspace
17108        .update(cx, |workspace, _, cx| {
17109            workspace.project().update(cx, |project, cx| {
17110                project.worktrees(cx).next().unwrap().read(cx).id()
17111            })
17112        })
17113        .unwrap();
17114
17115    let buffer = project
17116        .update(cx, |project, cx| {
17117            project.open_local_buffer(path!("/a/main.rs"), cx)
17118        })
17119        .await
17120        .unwrap();
17121    let editor_handle = workspace
17122        .update(cx, |workspace, window, cx| {
17123            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17124        })
17125        .unwrap()
17126        .await
17127        .unwrap()
17128        .downcast::<Editor>()
17129        .unwrap();
17130
17131    cx.executor().start_waiting();
17132    let fake_server = fake_servers.next().await.unwrap();
17133
17134    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17135        |params, _| async move {
17136            assert_eq!(
17137                params.text_document_position.text_document.uri,
17138                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17139            );
17140            assert_eq!(
17141                params.text_document_position.position,
17142                lsp::Position::new(0, 21),
17143            );
17144
17145            Ok(Some(vec![lsp::TextEdit {
17146                new_text: "]".to_string(),
17147                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17148            }]))
17149        },
17150    );
17151
17152    editor_handle.update_in(cx, |editor, window, cx| {
17153        window.focus(&editor.focus_handle(cx));
17154        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17155            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17156        });
17157        editor.handle_input("{", window, cx);
17158    });
17159
17160    cx.executor().run_until_parked();
17161
17162    buffer.update(cx, |buffer, _| {
17163        assert_eq!(
17164            buffer.text(),
17165            "fn main() { let a = {5}; }",
17166            "No extra braces from on type formatting should appear in the buffer"
17167        )
17168    });
17169}
17170
17171#[gpui::test(iterations = 20, seeds(31))]
17172async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17173    init_test(cx, |_| {});
17174
17175    let mut cx = EditorLspTestContext::new_rust(
17176        lsp::ServerCapabilities {
17177            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17178                first_trigger_character: ".".to_string(),
17179                more_trigger_character: None,
17180            }),
17181            ..Default::default()
17182        },
17183        cx,
17184    )
17185    .await;
17186
17187    cx.update_buffer(|buffer, _| {
17188        // This causes autoindent to be async.
17189        buffer.set_sync_parse_timeout(Duration::ZERO)
17190    });
17191
17192    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17193    cx.simulate_keystroke("\n");
17194    cx.run_until_parked();
17195
17196    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17197    let mut request =
17198        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17199            let buffer_cloned = buffer_cloned.clone();
17200            async move {
17201                buffer_cloned.update(&mut cx, |buffer, _| {
17202                    assert_eq!(
17203                        buffer.text(),
17204                        "fn c() {\n    d()\n        .\n}\n",
17205                        "OnTypeFormatting should triggered after autoindent applied"
17206                    )
17207                })?;
17208
17209                Ok(Some(vec![]))
17210            }
17211        });
17212
17213    cx.simulate_keystroke(".");
17214    cx.run_until_parked();
17215
17216    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17217    assert!(request.next().await.is_some());
17218    request.close();
17219    assert!(request.next().await.is_none());
17220}
17221
17222#[gpui::test]
17223async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17224    init_test(cx, |_| {});
17225
17226    let fs = FakeFs::new(cx.executor());
17227    fs.insert_tree(
17228        path!("/a"),
17229        json!({
17230            "main.rs": "fn main() { let a = 5; }",
17231            "other.rs": "// Test file",
17232        }),
17233    )
17234    .await;
17235
17236    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17237
17238    let server_restarts = Arc::new(AtomicUsize::new(0));
17239    let closure_restarts = Arc::clone(&server_restarts);
17240    let language_server_name = "test language server";
17241    let language_name: LanguageName = "Rust".into();
17242
17243    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17244    language_registry.add(Arc::new(Language::new(
17245        LanguageConfig {
17246            name: language_name.clone(),
17247            matcher: LanguageMatcher {
17248                path_suffixes: vec!["rs".to_string()],
17249                ..Default::default()
17250            },
17251            ..Default::default()
17252        },
17253        Some(tree_sitter_rust::LANGUAGE.into()),
17254    )));
17255    let mut fake_servers = language_registry.register_fake_lsp(
17256        "Rust",
17257        FakeLspAdapter {
17258            name: language_server_name,
17259            initialization_options: Some(json!({
17260                "testOptionValue": true
17261            })),
17262            initializer: Some(Box::new(move |fake_server| {
17263                let task_restarts = Arc::clone(&closure_restarts);
17264                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17265                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17266                    futures::future::ready(Ok(()))
17267                });
17268            })),
17269            ..Default::default()
17270        },
17271    );
17272
17273    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17274    let _buffer = project
17275        .update(cx, |project, cx| {
17276            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17277        })
17278        .await
17279        .unwrap();
17280    let _fake_server = fake_servers.next().await.unwrap();
17281    update_test_language_settings(cx, |language_settings| {
17282        language_settings.languages.0.insert(
17283            language_name.clone().0,
17284            LanguageSettingsContent {
17285                tab_size: NonZeroU32::new(8),
17286                ..Default::default()
17287            },
17288        );
17289    });
17290    cx.executor().run_until_parked();
17291    assert_eq!(
17292        server_restarts.load(atomic::Ordering::Acquire),
17293        0,
17294        "Should not restart LSP server on an unrelated change"
17295    );
17296
17297    update_test_project_settings(cx, |project_settings| {
17298        project_settings.lsp.insert(
17299            "Some other server name".into(),
17300            LspSettings {
17301                binary: None,
17302                settings: None,
17303                initialization_options: Some(json!({
17304                    "some other init value": false
17305                })),
17306                enable_lsp_tasks: false,
17307                fetch: None,
17308            },
17309        );
17310    });
17311    cx.executor().run_until_parked();
17312    assert_eq!(
17313        server_restarts.load(atomic::Ordering::Acquire),
17314        0,
17315        "Should not restart LSP server on an unrelated LSP settings change"
17316    );
17317
17318    update_test_project_settings(cx, |project_settings| {
17319        project_settings.lsp.insert(
17320            language_server_name.into(),
17321            LspSettings {
17322                binary: None,
17323                settings: None,
17324                initialization_options: Some(json!({
17325                    "anotherInitValue": false
17326                })),
17327                enable_lsp_tasks: false,
17328                fetch: None,
17329            },
17330        );
17331    });
17332    cx.executor().run_until_parked();
17333    assert_eq!(
17334        server_restarts.load(atomic::Ordering::Acquire),
17335        1,
17336        "Should restart LSP server on a related LSP settings change"
17337    );
17338
17339    update_test_project_settings(cx, |project_settings| {
17340        project_settings.lsp.insert(
17341            language_server_name.into(),
17342            LspSettings {
17343                binary: None,
17344                settings: None,
17345                initialization_options: Some(json!({
17346                    "anotherInitValue": false
17347                })),
17348                enable_lsp_tasks: false,
17349                fetch: None,
17350            },
17351        );
17352    });
17353    cx.executor().run_until_parked();
17354    assert_eq!(
17355        server_restarts.load(atomic::Ordering::Acquire),
17356        1,
17357        "Should not restart LSP server on a related LSP settings change that is the same"
17358    );
17359
17360    update_test_project_settings(cx, |project_settings| {
17361        project_settings.lsp.insert(
17362            language_server_name.into(),
17363            LspSettings {
17364                binary: None,
17365                settings: None,
17366                initialization_options: None,
17367                enable_lsp_tasks: false,
17368                fetch: None,
17369            },
17370        );
17371    });
17372    cx.executor().run_until_parked();
17373    assert_eq!(
17374        server_restarts.load(atomic::Ordering::Acquire),
17375        2,
17376        "Should restart LSP server on another related LSP settings change"
17377    );
17378}
17379
17380#[gpui::test]
17381async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17382    init_test(cx, |_| {});
17383
17384    let mut cx = EditorLspTestContext::new_rust(
17385        lsp::ServerCapabilities {
17386            completion_provider: Some(lsp::CompletionOptions {
17387                trigger_characters: Some(vec![".".to_string()]),
17388                resolve_provider: Some(true),
17389                ..Default::default()
17390            }),
17391            ..Default::default()
17392        },
17393        cx,
17394    )
17395    .await;
17396
17397    cx.set_state("fn main() { let a = 2ˇ; }");
17398    cx.simulate_keystroke(".");
17399    let completion_item = lsp::CompletionItem {
17400        label: "some".into(),
17401        kind: Some(lsp::CompletionItemKind::SNIPPET),
17402        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17403        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17404            kind: lsp::MarkupKind::Markdown,
17405            value: "```rust\nSome(2)\n```".to_string(),
17406        })),
17407        deprecated: Some(false),
17408        sort_text: Some("fffffff2".to_string()),
17409        filter_text: Some("some".to_string()),
17410        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17411        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17412            range: lsp::Range {
17413                start: lsp::Position {
17414                    line: 0,
17415                    character: 22,
17416                },
17417                end: lsp::Position {
17418                    line: 0,
17419                    character: 22,
17420                },
17421            },
17422            new_text: "Some(2)".to_string(),
17423        })),
17424        additional_text_edits: Some(vec![lsp::TextEdit {
17425            range: lsp::Range {
17426                start: lsp::Position {
17427                    line: 0,
17428                    character: 20,
17429                },
17430                end: lsp::Position {
17431                    line: 0,
17432                    character: 22,
17433                },
17434            },
17435            new_text: "".to_string(),
17436        }]),
17437        ..Default::default()
17438    };
17439
17440    let closure_completion_item = completion_item.clone();
17441    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17442        let task_completion_item = closure_completion_item.clone();
17443        async move {
17444            Ok(Some(lsp::CompletionResponse::Array(vec![
17445                task_completion_item,
17446            ])))
17447        }
17448    });
17449
17450    request.next().await;
17451
17452    cx.condition(|editor, _| editor.context_menu_visible())
17453        .await;
17454    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17455        editor
17456            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17457            .unwrap()
17458    });
17459    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17460
17461    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17462        let task_completion_item = completion_item.clone();
17463        async move { Ok(task_completion_item) }
17464    })
17465    .next()
17466    .await
17467    .unwrap();
17468    apply_additional_edits.await.unwrap();
17469    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17470}
17471
17472#[gpui::test]
17473async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17474    init_test(cx, |_| {});
17475
17476    let mut cx = EditorLspTestContext::new_rust(
17477        lsp::ServerCapabilities {
17478            completion_provider: Some(lsp::CompletionOptions {
17479                trigger_characters: Some(vec![".".to_string()]),
17480                resolve_provider: Some(true),
17481                ..Default::default()
17482            }),
17483            ..Default::default()
17484        },
17485        cx,
17486    )
17487    .await;
17488
17489    cx.set_state("fn main() { let a = 2ˇ; }");
17490    cx.simulate_keystroke(".");
17491
17492    let item1 = lsp::CompletionItem {
17493        label: "method id()".to_string(),
17494        filter_text: Some("id".to_string()),
17495        detail: None,
17496        documentation: None,
17497        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17498            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17499            new_text: ".id".to_string(),
17500        })),
17501        ..lsp::CompletionItem::default()
17502    };
17503
17504    let item2 = lsp::CompletionItem {
17505        label: "other".to_string(),
17506        filter_text: Some("other".to_string()),
17507        detail: None,
17508        documentation: None,
17509        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17510            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17511            new_text: ".other".to_string(),
17512        })),
17513        ..lsp::CompletionItem::default()
17514    };
17515
17516    let item1 = item1.clone();
17517    cx.set_request_handler::<lsp::request::Completion, _, _>({
17518        let item1 = item1.clone();
17519        move |_, _, _| {
17520            let item1 = item1.clone();
17521            let item2 = item2.clone();
17522            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17523        }
17524    })
17525    .next()
17526    .await;
17527
17528    cx.condition(|editor, _| editor.context_menu_visible())
17529        .await;
17530    cx.update_editor(|editor, _, _| {
17531        let context_menu = editor.context_menu.borrow_mut();
17532        let context_menu = context_menu
17533            .as_ref()
17534            .expect("Should have the context menu deployed");
17535        match context_menu {
17536            CodeContextMenu::Completions(completions_menu) => {
17537                let completions = completions_menu.completions.borrow_mut();
17538                assert_eq!(
17539                    completions
17540                        .iter()
17541                        .map(|completion| &completion.label.text)
17542                        .collect::<Vec<_>>(),
17543                    vec!["method id()", "other"]
17544                )
17545            }
17546            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17547        }
17548    });
17549
17550    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17551        let item1 = item1.clone();
17552        move |_, item_to_resolve, _| {
17553            let item1 = item1.clone();
17554            async move {
17555                if item1 == item_to_resolve {
17556                    Ok(lsp::CompletionItem {
17557                        label: "method id()".to_string(),
17558                        filter_text: Some("id".to_string()),
17559                        detail: Some("Now resolved!".to_string()),
17560                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17561                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17562                            range: lsp::Range::new(
17563                                lsp::Position::new(0, 22),
17564                                lsp::Position::new(0, 22),
17565                            ),
17566                            new_text: ".id".to_string(),
17567                        })),
17568                        ..lsp::CompletionItem::default()
17569                    })
17570                } else {
17571                    Ok(item_to_resolve)
17572                }
17573            }
17574        }
17575    })
17576    .next()
17577    .await
17578    .unwrap();
17579    cx.run_until_parked();
17580
17581    cx.update_editor(|editor, window, cx| {
17582        editor.context_menu_next(&Default::default(), window, cx);
17583    });
17584
17585    cx.update_editor(|editor, _, _| {
17586        let context_menu = editor.context_menu.borrow_mut();
17587        let context_menu = context_menu
17588            .as_ref()
17589            .expect("Should have the context menu deployed");
17590        match context_menu {
17591            CodeContextMenu::Completions(completions_menu) => {
17592                let completions = completions_menu.completions.borrow_mut();
17593                assert_eq!(
17594                    completions
17595                        .iter()
17596                        .map(|completion| &completion.label.text)
17597                        .collect::<Vec<_>>(),
17598                    vec!["method id() Now resolved!", "other"],
17599                    "Should update first completion label, but not second as the filter text did not match."
17600                );
17601            }
17602            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17603        }
17604    });
17605}
17606
17607#[gpui::test]
17608async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17609    init_test(cx, |_| {});
17610    let mut cx = EditorLspTestContext::new_rust(
17611        lsp::ServerCapabilities {
17612            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17613            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17614            completion_provider: Some(lsp::CompletionOptions {
17615                resolve_provider: Some(true),
17616                ..Default::default()
17617            }),
17618            ..Default::default()
17619        },
17620        cx,
17621    )
17622    .await;
17623    cx.set_state(indoc! {"
17624        struct TestStruct {
17625            field: i32
17626        }
17627
17628        fn mainˇ() {
17629            let unused_var = 42;
17630            let test_struct = TestStruct { field: 42 };
17631        }
17632    "});
17633    let symbol_range = cx.lsp_range(indoc! {"
17634        struct TestStruct {
17635            field: i32
17636        }
17637
17638        «fn main»() {
17639            let unused_var = 42;
17640            let test_struct = TestStruct { field: 42 };
17641        }
17642    "});
17643    let mut hover_requests =
17644        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17645            Ok(Some(lsp::Hover {
17646                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17647                    kind: lsp::MarkupKind::Markdown,
17648                    value: "Function documentation".to_string(),
17649                }),
17650                range: Some(symbol_range),
17651            }))
17652        });
17653
17654    // Case 1: Test that code action menu hide hover popover
17655    cx.dispatch_action(Hover);
17656    hover_requests.next().await;
17657    cx.condition(|editor, _| editor.hover_state.visible()).await;
17658    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17659        move |_, _, _| async move {
17660            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17661                lsp::CodeAction {
17662                    title: "Remove unused variable".to_string(),
17663                    kind: Some(CodeActionKind::QUICKFIX),
17664                    edit: Some(lsp::WorkspaceEdit {
17665                        changes: Some(
17666                            [(
17667                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17668                                vec![lsp::TextEdit {
17669                                    range: lsp::Range::new(
17670                                        lsp::Position::new(5, 4),
17671                                        lsp::Position::new(5, 27),
17672                                    ),
17673                                    new_text: "".to_string(),
17674                                }],
17675                            )]
17676                            .into_iter()
17677                            .collect(),
17678                        ),
17679                        ..Default::default()
17680                    }),
17681                    ..Default::default()
17682                },
17683            )]))
17684        },
17685    );
17686    cx.update_editor(|editor, window, cx| {
17687        editor.toggle_code_actions(
17688            &ToggleCodeActions {
17689                deployed_from: None,
17690                quick_launch: false,
17691            },
17692            window,
17693            cx,
17694        );
17695    });
17696    code_action_requests.next().await;
17697    cx.run_until_parked();
17698    cx.condition(|editor, _| editor.context_menu_visible())
17699        .await;
17700    cx.update_editor(|editor, _, _| {
17701        assert!(
17702            !editor.hover_state.visible(),
17703            "Hover popover should be hidden when code action menu is shown"
17704        );
17705        // Hide code actions
17706        editor.context_menu.take();
17707    });
17708
17709    // Case 2: Test that code completions hide hover popover
17710    cx.dispatch_action(Hover);
17711    hover_requests.next().await;
17712    cx.condition(|editor, _| editor.hover_state.visible()).await;
17713    let counter = Arc::new(AtomicUsize::new(0));
17714    let mut completion_requests =
17715        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17716            let counter = counter.clone();
17717            async move {
17718                counter.fetch_add(1, atomic::Ordering::Release);
17719                Ok(Some(lsp::CompletionResponse::Array(vec![
17720                    lsp::CompletionItem {
17721                        label: "main".into(),
17722                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17723                        detail: Some("() -> ()".to_string()),
17724                        ..Default::default()
17725                    },
17726                    lsp::CompletionItem {
17727                        label: "TestStruct".into(),
17728                        kind: Some(lsp::CompletionItemKind::STRUCT),
17729                        detail: Some("struct TestStruct".to_string()),
17730                        ..Default::default()
17731                    },
17732                ])))
17733            }
17734        });
17735    cx.update_editor(|editor, window, cx| {
17736        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17737    });
17738    completion_requests.next().await;
17739    cx.condition(|editor, _| editor.context_menu_visible())
17740        .await;
17741    cx.update_editor(|editor, _, _| {
17742        assert!(
17743            !editor.hover_state.visible(),
17744            "Hover popover should be hidden when completion menu is shown"
17745        );
17746    });
17747}
17748
17749#[gpui::test]
17750async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17751    init_test(cx, |_| {});
17752
17753    let mut cx = EditorLspTestContext::new_rust(
17754        lsp::ServerCapabilities {
17755            completion_provider: Some(lsp::CompletionOptions {
17756                trigger_characters: Some(vec![".".to_string()]),
17757                resolve_provider: Some(true),
17758                ..Default::default()
17759            }),
17760            ..Default::default()
17761        },
17762        cx,
17763    )
17764    .await;
17765
17766    cx.set_state("fn main() { let a = 2ˇ; }");
17767    cx.simulate_keystroke(".");
17768
17769    let unresolved_item_1 = lsp::CompletionItem {
17770        label: "id".to_string(),
17771        filter_text: Some("id".to_string()),
17772        detail: None,
17773        documentation: None,
17774        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17775            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17776            new_text: ".id".to_string(),
17777        })),
17778        ..lsp::CompletionItem::default()
17779    };
17780    let resolved_item_1 = lsp::CompletionItem {
17781        additional_text_edits: Some(vec![lsp::TextEdit {
17782            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17783            new_text: "!!".to_string(),
17784        }]),
17785        ..unresolved_item_1.clone()
17786    };
17787    let unresolved_item_2 = lsp::CompletionItem {
17788        label: "other".to_string(),
17789        filter_text: Some("other".to_string()),
17790        detail: None,
17791        documentation: None,
17792        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17793            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17794            new_text: ".other".to_string(),
17795        })),
17796        ..lsp::CompletionItem::default()
17797    };
17798    let resolved_item_2 = lsp::CompletionItem {
17799        additional_text_edits: Some(vec![lsp::TextEdit {
17800            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17801            new_text: "??".to_string(),
17802        }]),
17803        ..unresolved_item_2.clone()
17804    };
17805
17806    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17807    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17808    cx.lsp
17809        .server
17810        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17811            let unresolved_item_1 = unresolved_item_1.clone();
17812            let resolved_item_1 = resolved_item_1.clone();
17813            let unresolved_item_2 = unresolved_item_2.clone();
17814            let resolved_item_2 = resolved_item_2.clone();
17815            let resolve_requests_1 = resolve_requests_1.clone();
17816            let resolve_requests_2 = resolve_requests_2.clone();
17817            move |unresolved_request, _| {
17818                let unresolved_item_1 = unresolved_item_1.clone();
17819                let resolved_item_1 = resolved_item_1.clone();
17820                let unresolved_item_2 = unresolved_item_2.clone();
17821                let resolved_item_2 = resolved_item_2.clone();
17822                let resolve_requests_1 = resolve_requests_1.clone();
17823                let resolve_requests_2 = resolve_requests_2.clone();
17824                async move {
17825                    if unresolved_request == unresolved_item_1 {
17826                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17827                        Ok(resolved_item_1.clone())
17828                    } else if unresolved_request == unresolved_item_2 {
17829                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17830                        Ok(resolved_item_2.clone())
17831                    } else {
17832                        panic!("Unexpected completion item {unresolved_request:?}")
17833                    }
17834                }
17835            }
17836        })
17837        .detach();
17838
17839    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17840        let unresolved_item_1 = unresolved_item_1.clone();
17841        let unresolved_item_2 = unresolved_item_2.clone();
17842        async move {
17843            Ok(Some(lsp::CompletionResponse::Array(vec![
17844                unresolved_item_1,
17845                unresolved_item_2,
17846            ])))
17847        }
17848    })
17849    .next()
17850    .await;
17851
17852    cx.condition(|editor, _| editor.context_menu_visible())
17853        .await;
17854    cx.update_editor(|editor, _, _| {
17855        let context_menu = editor.context_menu.borrow_mut();
17856        let context_menu = context_menu
17857            .as_ref()
17858            .expect("Should have the context menu deployed");
17859        match context_menu {
17860            CodeContextMenu::Completions(completions_menu) => {
17861                let completions = completions_menu.completions.borrow_mut();
17862                assert_eq!(
17863                    completions
17864                        .iter()
17865                        .map(|completion| &completion.label.text)
17866                        .collect::<Vec<_>>(),
17867                    vec!["id", "other"]
17868                )
17869            }
17870            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17871        }
17872    });
17873    cx.run_until_parked();
17874
17875    cx.update_editor(|editor, window, cx| {
17876        editor.context_menu_next(&ContextMenuNext, window, cx);
17877    });
17878    cx.run_until_parked();
17879    cx.update_editor(|editor, window, cx| {
17880        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17881    });
17882    cx.run_until_parked();
17883    cx.update_editor(|editor, window, cx| {
17884        editor.context_menu_next(&ContextMenuNext, window, cx);
17885    });
17886    cx.run_until_parked();
17887    cx.update_editor(|editor, window, cx| {
17888        editor
17889            .compose_completion(&ComposeCompletion::default(), window, cx)
17890            .expect("No task returned")
17891    })
17892    .await
17893    .expect("Completion failed");
17894    cx.run_until_parked();
17895
17896    cx.update_editor(|editor, _, cx| {
17897        assert_eq!(
17898            resolve_requests_1.load(atomic::Ordering::Acquire),
17899            1,
17900            "Should always resolve once despite multiple selections"
17901        );
17902        assert_eq!(
17903            resolve_requests_2.load(atomic::Ordering::Acquire),
17904            1,
17905            "Should always resolve once after multiple selections and applying the completion"
17906        );
17907        assert_eq!(
17908            editor.text(cx),
17909            "fn main() { let a = ??.other; }",
17910            "Should use resolved data when applying the completion"
17911        );
17912    });
17913}
17914
17915#[gpui::test]
17916async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17917    init_test(cx, |_| {});
17918
17919    let item_0 = lsp::CompletionItem {
17920        label: "abs".into(),
17921        insert_text: Some("abs".into()),
17922        data: Some(json!({ "very": "special"})),
17923        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17924        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17925            lsp::InsertReplaceEdit {
17926                new_text: "abs".to_string(),
17927                insert: lsp::Range::default(),
17928                replace: lsp::Range::default(),
17929            },
17930        )),
17931        ..lsp::CompletionItem::default()
17932    };
17933    let items = iter::once(item_0.clone())
17934        .chain((11..51).map(|i| lsp::CompletionItem {
17935            label: format!("item_{}", i),
17936            insert_text: Some(format!("item_{}", i)),
17937            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17938            ..lsp::CompletionItem::default()
17939        }))
17940        .collect::<Vec<_>>();
17941
17942    let default_commit_characters = vec!["?".to_string()];
17943    let default_data = json!({ "default": "data"});
17944    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17945    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17946    let default_edit_range = lsp::Range {
17947        start: lsp::Position {
17948            line: 0,
17949            character: 5,
17950        },
17951        end: lsp::Position {
17952            line: 0,
17953            character: 5,
17954        },
17955    };
17956
17957    let mut cx = EditorLspTestContext::new_rust(
17958        lsp::ServerCapabilities {
17959            completion_provider: Some(lsp::CompletionOptions {
17960                trigger_characters: Some(vec![".".to_string()]),
17961                resolve_provider: Some(true),
17962                ..Default::default()
17963            }),
17964            ..Default::default()
17965        },
17966        cx,
17967    )
17968    .await;
17969
17970    cx.set_state("fn main() { let a = 2ˇ; }");
17971    cx.simulate_keystroke(".");
17972
17973    let completion_data = default_data.clone();
17974    let completion_characters = default_commit_characters.clone();
17975    let completion_items = items.clone();
17976    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17977        let default_data = completion_data.clone();
17978        let default_commit_characters = completion_characters.clone();
17979        let items = completion_items.clone();
17980        async move {
17981            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17982                items,
17983                item_defaults: Some(lsp::CompletionListItemDefaults {
17984                    data: Some(default_data.clone()),
17985                    commit_characters: Some(default_commit_characters.clone()),
17986                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17987                        default_edit_range,
17988                    )),
17989                    insert_text_format: Some(default_insert_text_format),
17990                    insert_text_mode: Some(default_insert_text_mode),
17991                }),
17992                ..lsp::CompletionList::default()
17993            })))
17994        }
17995    })
17996    .next()
17997    .await;
17998
17999    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18000    cx.lsp
18001        .server
18002        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18003            let closure_resolved_items = resolved_items.clone();
18004            move |item_to_resolve, _| {
18005                let closure_resolved_items = closure_resolved_items.clone();
18006                async move {
18007                    closure_resolved_items.lock().push(item_to_resolve.clone());
18008                    Ok(item_to_resolve)
18009                }
18010            }
18011        })
18012        .detach();
18013
18014    cx.condition(|editor, _| editor.context_menu_visible())
18015        .await;
18016    cx.run_until_parked();
18017    cx.update_editor(|editor, _, _| {
18018        let menu = editor.context_menu.borrow_mut();
18019        match menu.as_ref().expect("should have the completions menu") {
18020            CodeContextMenu::Completions(completions_menu) => {
18021                assert_eq!(
18022                    completions_menu
18023                        .entries
18024                        .borrow()
18025                        .iter()
18026                        .map(|mat| mat.string.clone())
18027                        .collect::<Vec<String>>(),
18028                    items
18029                        .iter()
18030                        .map(|completion| completion.label.clone())
18031                        .collect::<Vec<String>>()
18032                );
18033            }
18034            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18035        }
18036    });
18037    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18038    // with 4 from the end.
18039    assert_eq!(
18040        *resolved_items.lock(),
18041        [&items[0..16], &items[items.len() - 4..items.len()]]
18042            .concat()
18043            .iter()
18044            .cloned()
18045            .map(|mut item| {
18046                if item.data.is_none() {
18047                    item.data = Some(default_data.clone());
18048                }
18049                item
18050            })
18051            .collect::<Vec<lsp::CompletionItem>>(),
18052        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18053    );
18054    resolved_items.lock().clear();
18055
18056    cx.update_editor(|editor, window, cx| {
18057        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18058    });
18059    cx.run_until_parked();
18060    // Completions that have already been resolved are skipped.
18061    assert_eq!(
18062        *resolved_items.lock(),
18063        items[items.len() - 17..items.len() - 4]
18064            .iter()
18065            .cloned()
18066            .map(|mut item| {
18067                if item.data.is_none() {
18068                    item.data = Some(default_data.clone());
18069                }
18070                item
18071            })
18072            .collect::<Vec<lsp::CompletionItem>>()
18073    );
18074    resolved_items.lock().clear();
18075}
18076
18077#[gpui::test]
18078async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18079    init_test(cx, |_| {});
18080
18081    let mut cx = EditorLspTestContext::new(
18082        Language::new(
18083            LanguageConfig {
18084                matcher: LanguageMatcher {
18085                    path_suffixes: vec!["jsx".into()],
18086                    ..Default::default()
18087                },
18088                overrides: [(
18089                    "element".into(),
18090                    LanguageConfigOverride {
18091                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18092                        ..Default::default()
18093                    },
18094                )]
18095                .into_iter()
18096                .collect(),
18097                ..Default::default()
18098            },
18099            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18100        )
18101        .with_override_query("(jsx_self_closing_element) @element")
18102        .unwrap(),
18103        lsp::ServerCapabilities {
18104            completion_provider: Some(lsp::CompletionOptions {
18105                trigger_characters: Some(vec![":".to_string()]),
18106                ..Default::default()
18107            }),
18108            ..Default::default()
18109        },
18110        cx,
18111    )
18112    .await;
18113
18114    cx.lsp
18115        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18116            Ok(Some(lsp::CompletionResponse::Array(vec![
18117                lsp::CompletionItem {
18118                    label: "bg-blue".into(),
18119                    ..Default::default()
18120                },
18121                lsp::CompletionItem {
18122                    label: "bg-red".into(),
18123                    ..Default::default()
18124                },
18125                lsp::CompletionItem {
18126                    label: "bg-yellow".into(),
18127                    ..Default::default()
18128                },
18129            ])))
18130        });
18131
18132    cx.set_state(r#"<p class="bgˇ" />"#);
18133
18134    // Trigger completion when typing a dash, because the dash is an extra
18135    // word character in the 'element' scope, which contains the cursor.
18136    cx.simulate_keystroke("-");
18137    cx.executor().run_until_parked();
18138    cx.update_editor(|editor, _, _| {
18139        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18140        {
18141            assert_eq!(
18142                completion_menu_entries(menu),
18143                &["bg-blue", "bg-red", "bg-yellow"]
18144            );
18145        } else {
18146            panic!("expected completion menu to be open");
18147        }
18148    });
18149
18150    cx.simulate_keystroke("l");
18151    cx.executor().run_until_parked();
18152    cx.update_editor(|editor, _, _| {
18153        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18154        {
18155            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18156        } else {
18157            panic!("expected completion menu to be open");
18158        }
18159    });
18160
18161    // When filtering completions, consider the character after the '-' to
18162    // be the start of a subword.
18163    cx.set_state(r#"<p class="yelˇ" />"#);
18164    cx.simulate_keystroke("l");
18165    cx.executor().run_until_parked();
18166    cx.update_editor(|editor, _, _| {
18167        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18168        {
18169            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18170        } else {
18171            panic!("expected completion menu to be open");
18172        }
18173    });
18174}
18175
18176fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18177    let entries = menu.entries.borrow();
18178    entries.iter().map(|mat| mat.string.clone()).collect()
18179}
18180
18181#[gpui::test]
18182async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18183    init_test(cx, |settings| {
18184        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
18185    });
18186
18187    let fs = FakeFs::new(cx.executor());
18188    fs.insert_file(path!("/file.ts"), Default::default()).await;
18189
18190    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18191    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18192
18193    language_registry.add(Arc::new(Language::new(
18194        LanguageConfig {
18195            name: "TypeScript".into(),
18196            matcher: LanguageMatcher {
18197                path_suffixes: vec!["ts".to_string()],
18198                ..Default::default()
18199            },
18200            ..Default::default()
18201        },
18202        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18203    )));
18204    update_test_language_settings(cx, |settings| {
18205        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18206    });
18207
18208    let test_plugin = "test_plugin";
18209    let _ = language_registry.register_fake_lsp(
18210        "TypeScript",
18211        FakeLspAdapter {
18212            prettier_plugins: vec![test_plugin],
18213            ..Default::default()
18214        },
18215    );
18216
18217    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18218    let buffer = project
18219        .update(cx, |project, cx| {
18220            project.open_local_buffer(path!("/file.ts"), cx)
18221        })
18222        .await
18223        .unwrap();
18224
18225    let buffer_text = "one\ntwo\nthree\n";
18226    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18227    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18228    editor.update_in(cx, |editor, window, cx| {
18229        editor.set_text(buffer_text, window, cx)
18230    });
18231
18232    editor
18233        .update_in(cx, |editor, window, cx| {
18234            editor.perform_format(
18235                project.clone(),
18236                FormatTrigger::Manual,
18237                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18238                window,
18239                cx,
18240            )
18241        })
18242        .unwrap()
18243        .await;
18244    assert_eq!(
18245        editor.update(cx, |editor, cx| editor.text(cx)),
18246        buffer_text.to_string() + prettier_format_suffix,
18247        "Test prettier formatting was not applied to the original buffer text",
18248    );
18249
18250    update_test_language_settings(cx, |settings| {
18251        settings.defaults.formatter = Some(FormatterList::default())
18252    });
18253    let format = editor.update_in(cx, |editor, window, cx| {
18254        editor.perform_format(
18255            project.clone(),
18256            FormatTrigger::Manual,
18257            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18258            window,
18259            cx,
18260        )
18261    });
18262    format.await.unwrap();
18263    assert_eq!(
18264        editor.update(cx, |editor, cx| editor.text(cx)),
18265        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18266        "Autoformatting (via test prettier) was not applied to the original buffer text",
18267    );
18268}
18269
18270#[gpui::test]
18271async fn test_addition_reverts(cx: &mut TestAppContext) {
18272    init_test(cx, |_| {});
18273    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18274    let base_text = indoc! {r#"
18275        struct Row;
18276        struct Row1;
18277        struct Row2;
18278
18279        struct Row4;
18280        struct Row5;
18281        struct Row6;
18282
18283        struct Row8;
18284        struct Row9;
18285        struct Row10;"#};
18286
18287    // When addition hunks are not adjacent to carets, no hunk revert is performed
18288    assert_hunk_revert(
18289        indoc! {r#"struct Row;
18290                   struct Row1;
18291                   struct Row1.1;
18292                   struct Row1.2;
18293                   struct Row2;ˇ
18294
18295                   struct Row4;
18296                   struct Row5;
18297                   struct Row6;
18298
18299                   struct Row8;
18300                   ˇstruct Row9;
18301                   struct Row9.1;
18302                   struct Row9.2;
18303                   struct Row9.3;
18304                   struct Row10;"#},
18305        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18306        indoc! {r#"struct Row;
18307                   struct Row1;
18308                   struct Row1.1;
18309                   struct Row1.2;
18310                   struct Row2;ˇ
18311
18312                   struct Row4;
18313                   struct Row5;
18314                   struct Row6;
18315
18316                   struct Row8;
18317                   ˇstruct Row9;
18318                   struct Row9.1;
18319                   struct Row9.2;
18320                   struct Row9.3;
18321                   struct Row10;"#},
18322        base_text,
18323        &mut cx,
18324    );
18325    // Same for selections
18326    assert_hunk_revert(
18327        indoc! {r#"struct Row;
18328                   struct Row1;
18329                   struct Row2;
18330                   struct Row2.1;
18331                   struct Row2.2;
18332                   «ˇ
18333                   struct Row4;
18334                   struct» Row5;
18335                   «struct Row6;
18336                   ˇ»
18337                   struct Row9.1;
18338                   struct Row9.2;
18339                   struct Row9.3;
18340                   struct Row8;
18341                   struct Row9;
18342                   struct Row10;"#},
18343        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18344        indoc! {r#"struct Row;
18345                   struct Row1;
18346                   struct Row2;
18347                   struct Row2.1;
18348                   struct Row2.2;
18349                   «ˇ
18350                   struct Row4;
18351                   struct» Row5;
18352                   «struct Row6;
18353                   ˇ»
18354                   struct Row9.1;
18355                   struct Row9.2;
18356                   struct Row9.3;
18357                   struct Row8;
18358                   struct Row9;
18359                   struct Row10;"#},
18360        base_text,
18361        &mut cx,
18362    );
18363
18364    // When carets and selections intersect the addition hunks, those are reverted.
18365    // Adjacent carets got merged.
18366    assert_hunk_revert(
18367        indoc! {r#"struct Row;
18368                   ˇ// something on the top
18369                   struct Row1;
18370                   struct Row2;
18371                   struct Roˇw3.1;
18372                   struct Row2.2;
18373                   struct Row2.3;ˇ
18374
18375                   struct Row4;
18376                   struct ˇRow5.1;
18377                   struct Row5.2;
18378                   struct «Rowˇ»5.3;
18379                   struct Row5;
18380                   struct Row6;
18381                   ˇ
18382                   struct Row9.1;
18383                   struct «Rowˇ»9.2;
18384                   struct «ˇRow»9.3;
18385                   struct Row8;
18386                   struct Row9;
18387                   «ˇ// something on bottom»
18388                   struct Row10;"#},
18389        vec![
18390            DiffHunkStatusKind::Added,
18391            DiffHunkStatusKind::Added,
18392            DiffHunkStatusKind::Added,
18393            DiffHunkStatusKind::Added,
18394            DiffHunkStatusKind::Added,
18395        ],
18396        indoc! {r#"struct Row;
18397                   ˇstruct Row1;
18398                   struct Row2;
18399                   ˇ
18400                   struct Row4;
18401                   ˇstruct Row5;
18402                   struct Row6;
18403                   ˇ
18404                   ˇstruct Row8;
18405                   struct Row9;
18406                   ˇstruct Row10;"#},
18407        base_text,
18408        &mut cx,
18409    );
18410}
18411
18412#[gpui::test]
18413async fn test_modification_reverts(cx: &mut TestAppContext) {
18414    init_test(cx, |_| {});
18415    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18416    let base_text = indoc! {r#"
18417        struct Row;
18418        struct Row1;
18419        struct Row2;
18420
18421        struct Row4;
18422        struct Row5;
18423        struct Row6;
18424
18425        struct Row8;
18426        struct Row9;
18427        struct Row10;"#};
18428
18429    // Modification hunks behave the same as the addition ones.
18430    assert_hunk_revert(
18431        indoc! {r#"struct Row;
18432                   struct Row1;
18433                   struct Row33;
18434                   ˇ
18435                   struct Row4;
18436                   struct Row5;
18437                   struct Row6;
18438                   ˇ
18439                   struct Row99;
18440                   struct Row9;
18441                   struct Row10;"#},
18442        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18443        indoc! {r#"struct Row;
18444                   struct Row1;
18445                   struct Row33;
18446                   ˇ
18447                   struct Row4;
18448                   struct Row5;
18449                   struct Row6;
18450                   ˇ
18451                   struct Row99;
18452                   struct Row9;
18453                   struct Row10;"#},
18454        base_text,
18455        &mut cx,
18456    );
18457    assert_hunk_revert(
18458        indoc! {r#"struct Row;
18459                   struct Row1;
18460                   struct Row33;
18461                   «ˇ
18462                   struct Row4;
18463                   struct» Row5;
18464                   «struct Row6;
18465                   ˇ»
18466                   struct Row99;
18467                   struct Row9;
18468                   struct Row10;"#},
18469        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18470        indoc! {r#"struct Row;
18471                   struct Row1;
18472                   struct Row33;
18473                   «ˇ
18474                   struct Row4;
18475                   struct» Row5;
18476                   «struct Row6;
18477                   ˇ»
18478                   struct Row99;
18479                   struct Row9;
18480                   struct Row10;"#},
18481        base_text,
18482        &mut cx,
18483    );
18484
18485    assert_hunk_revert(
18486        indoc! {r#"ˇstruct Row1.1;
18487                   struct Row1;
18488                   «ˇstr»uct Row22;
18489
18490                   struct ˇRow44;
18491                   struct Row5;
18492                   struct «Rˇ»ow66;ˇ
18493
18494                   «struˇ»ct Row88;
18495                   struct Row9;
18496                   struct Row1011;ˇ"#},
18497        vec![
18498            DiffHunkStatusKind::Modified,
18499            DiffHunkStatusKind::Modified,
18500            DiffHunkStatusKind::Modified,
18501            DiffHunkStatusKind::Modified,
18502            DiffHunkStatusKind::Modified,
18503            DiffHunkStatusKind::Modified,
18504        ],
18505        indoc! {r#"struct Row;
18506                   ˇstruct Row1;
18507                   struct Row2;
18508                   ˇ
18509                   struct Row4;
18510                   ˇstruct Row5;
18511                   struct Row6;
18512                   ˇ
18513                   struct Row8;
18514                   ˇstruct Row9;
18515                   struct Row10;ˇ"#},
18516        base_text,
18517        &mut cx,
18518    );
18519}
18520
18521#[gpui::test]
18522async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18523    init_test(cx, |_| {});
18524    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18525    let base_text = indoc! {r#"
18526        one
18527
18528        two
18529        three
18530        "#};
18531
18532    cx.set_head_text(base_text);
18533    cx.set_state("\nˇ\n");
18534    cx.executor().run_until_parked();
18535    cx.update_editor(|editor, _window, cx| {
18536        editor.expand_selected_diff_hunks(cx);
18537    });
18538    cx.executor().run_until_parked();
18539    cx.update_editor(|editor, window, cx| {
18540        editor.backspace(&Default::default(), window, cx);
18541    });
18542    cx.run_until_parked();
18543    cx.assert_state_with_diff(
18544        indoc! {r#"
18545
18546        - two
18547        - threeˇ
18548        +
18549        "#}
18550        .to_string(),
18551    );
18552}
18553
18554#[gpui::test]
18555async fn test_deletion_reverts(cx: &mut TestAppContext) {
18556    init_test(cx, |_| {});
18557    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18558    let base_text = indoc! {r#"struct Row;
18559struct Row1;
18560struct Row2;
18561
18562struct Row4;
18563struct Row5;
18564struct Row6;
18565
18566struct Row8;
18567struct Row9;
18568struct Row10;"#};
18569
18570    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18571    assert_hunk_revert(
18572        indoc! {r#"struct Row;
18573                   struct Row2;
18574
18575                   ˇstruct Row4;
18576                   struct Row5;
18577                   struct Row6;
18578                   ˇ
18579                   struct Row8;
18580                   struct Row10;"#},
18581        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18582        indoc! {r#"struct Row;
18583                   struct Row2;
18584
18585                   ˇstruct Row4;
18586                   struct Row5;
18587                   struct Row6;
18588                   ˇ
18589                   struct Row8;
18590                   struct Row10;"#},
18591        base_text,
18592        &mut cx,
18593    );
18594    assert_hunk_revert(
18595        indoc! {r#"struct Row;
18596                   struct Row2;
18597
18598                   «ˇstruct Row4;
18599                   struct» Row5;
18600                   «struct Row6;
18601                   ˇ»
18602                   struct Row8;
18603                   struct Row10;"#},
18604        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18605        indoc! {r#"struct Row;
18606                   struct Row2;
18607
18608                   «ˇstruct Row4;
18609                   struct» Row5;
18610                   «struct Row6;
18611                   ˇ»
18612                   struct Row8;
18613                   struct Row10;"#},
18614        base_text,
18615        &mut cx,
18616    );
18617
18618    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18619    assert_hunk_revert(
18620        indoc! {r#"struct Row;
18621                   ˇstruct Row2;
18622
18623                   struct Row4;
18624                   struct Row5;
18625                   struct Row6;
18626
18627                   struct Row8;ˇ
18628                   struct Row10;"#},
18629        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18630        indoc! {r#"struct Row;
18631                   struct Row1;
18632                   ˇstruct Row2;
18633
18634                   struct Row4;
18635                   struct Row5;
18636                   struct Row6;
18637
18638                   struct Row8;ˇ
18639                   struct Row9;
18640                   struct Row10;"#},
18641        base_text,
18642        &mut cx,
18643    );
18644    assert_hunk_revert(
18645        indoc! {r#"struct Row;
18646                   struct Row2«ˇ;
18647                   struct Row4;
18648                   struct» Row5;
18649                   «struct Row6;
18650
18651                   struct Row8;ˇ»
18652                   struct Row10;"#},
18653        vec![
18654            DiffHunkStatusKind::Deleted,
18655            DiffHunkStatusKind::Deleted,
18656            DiffHunkStatusKind::Deleted,
18657        ],
18658        indoc! {r#"struct Row;
18659                   struct Row1;
18660                   struct Row2«ˇ;
18661
18662                   struct Row4;
18663                   struct» Row5;
18664                   «struct Row6;
18665
18666                   struct Row8;ˇ»
18667                   struct Row9;
18668                   struct Row10;"#},
18669        base_text,
18670        &mut cx,
18671    );
18672}
18673
18674#[gpui::test]
18675async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18676    init_test(cx, |_| {});
18677
18678    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18679    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18680    let base_text_3 =
18681        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18682
18683    let text_1 = edit_first_char_of_every_line(base_text_1);
18684    let text_2 = edit_first_char_of_every_line(base_text_2);
18685    let text_3 = edit_first_char_of_every_line(base_text_3);
18686
18687    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18688    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18689    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18690
18691    let multibuffer = cx.new(|cx| {
18692        let mut multibuffer = MultiBuffer::new(ReadWrite);
18693        multibuffer.push_excerpts(
18694            buffer_1.clone(),
18695            [
18696                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18697                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18698                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18699            ],
18700            cx,
18701        );
18702        multibuffer.push_excerpts(
18703            buffer_2.clone(),
18704            [
18705                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18706                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18707                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18708            ],
18709            cx,
18710        );
18711        multibuffer.push_excerpts(
18712            buffer_3.clone(),
18713            [
18714                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18715                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18716                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18717            ],
18718            cx,
18719        );
18720        multibuffer
18721    });
18722
18723    let fs = FakeFs::new(cx.executor());
18724    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18725    let (editor, cx) = cx
18726        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18727    editor.update_in(cx, |editor, _window, cx| {
18728        for (buffer, diff_base) in [
18729            (buffer_1.clone(), base_text_1),
18730            (buffer_2.clone(), base_text_2),
18731            (buffer_3.clone(), base_text_3),
18732        ] {
18733            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18734            editor
18735                .buffer
18736                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18737        }
18738    });
18739    cx.executor().run_until_parked();
18740
18741    editor.update_in(cx, |editor, window, cx| {
18742        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}");
18743        editor.select_all(&SelectAll, window, cx);
18744        editor.git_restore(&Default::default(), window, cx);
18745    });
18746    cx.executor().run_until_parked();
18747
18748    // When all ranges are selected, all buffer hunks are reverted.
18749    editor.update(cx, |editor, cx| {
18750        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");
18751    });
18752    buffer_1.update(cx, |buffer, _| {
18753        assert_eq!(buffer.text(), base_text_1);
18754    });
18755    buffer_2.update(cx, |buffer, _| {
18756        assert_eq!(buffer.text(), base_text_2);
18757    });
18758    buffer_3.update(cx, |buffer, _| {
18759        assert_eq!(buffer.text(), base_text_3);
18760    });
18761
18762    editor.update_in(cx, |editor, window, cx| {
18763        editor.undo(&Default::default(), window, cx);
18764    });
18765
18766    editor.update_in(cx, |editor, window, cx| {
18767        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18768            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18769        });
18770        editor.git_restore(&Default::default(), window, cx);
18771    });
18772
18773    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18774    // but not affect buffer_2 and its related excerpts.
18775    editor.update(cx, |editor, cx| {
18776        assert_eq!(
18777            editor.text(cx),
18778            "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}"
18779        );
18780    });
18781    buffer_1.update(cx, |buffer, _| {
18782        assert_eq!(buffer.text(), base_text_1);
18783    });
18784    buffer_2.update(cx, |buffer, _| {
18785        assert_eq!(
18786            buffer.text(),
18787            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18788        );
18789    });
18790    buffer_3.update(cx, |buffer, _| {
18791        assert_eq!(
18792            buffer.text(),
18793            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18794        );
18795    });
18796
18797    fn edit_first_char_of_every_line(text: &str) -> String {
18798        text.split('\n')
18799            .map(|line| format!("X{}", &line[1..]))
18800            .collect::<Vec<_>>()
18801            .join("\n")
18802    }
18803}
18804
18805#[gpui::test]
18806async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18807    init_test(cx, |_| {});
18808
18809    let cols = 4;
18810    let rows = 10;
18811    let sample_text_1 = sample_text(rows, cols, 'a');
18812    assert_eq!(
18813        sample_text_1,
18814        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18815    );
18816    let sample_text_2 = sample_text(rows, cols, 'l');
18817    assert_eq!(
18818        sample_text_2,
18819        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18820    );
18821    let sample_text_3 = sample_text(rows, cols, 'v');
18822    assert_eq!(
18823        sample_text_3,
18824        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18825    );
18826
18827    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18828    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18829    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18830
18831    let multi_buffer = cx.new(|cx| {
18832        let mut multibuffer = MultiBuffer::new(ReadWrite);
18833        multibuffer.push_excerpts(
18834            buffer_1.clone(),
18835            [
18836                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18837                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18838                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18839            ],
18840            cx,
18841        );
18842        multibuffer.push_excerpts(
18843            buffer_2.clone(),
18844            [
18845                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18846                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18847                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18848            ],
18849            cx,
18850        );
18851        multibuffer.push_excerpts(
18852            buffer_3.clone(),
18853            [
18854                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18855                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18856                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18857            ],
18858            cx,
18859        );
18860        multibuffer
18861    });
18862
18863    let fs = FakeFs::new(cx.executor());
18864    fs.insert_tree(
18865        "/a",
18866        json!({
18867            "main.rs": sample_text_1,
18868            "other.rs": sample_text_2,
18869            "lib.rs": sample_text_3,
18870        }),
18871    )
18872    .await;
18873    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18874    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18875    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18876    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18877        Editor::new(
18878            EditorMode::full(),
18879            multi_buffer,
18880            Some(project.clone()),
18881            window,
18882            cx,
18883        )
18884    });
18885    let multibuffer_item_id = workspace
18886        .update(cx, |workspace, window, cx| {
18887            assert!(
18888                workspace.active_item(cx).is_none(),
18889                "active item should be None before the first item is added"
18890            );
18891            workspace.add_item_to_active_pane(
18892                Box::new(multi_buffer_editor.clone()),
18893                None,
18894                true,
18895                window,
18896                cx,
18897            );
18898            let active_item = workspace
18899                .active_item(cx)
18900                .expect("should have an active item after adding the multi buffer");
18901            assert_eq!(
18902                active_item.buffer_kind(cx),
18903                ItemBufferKind::Multibuffer,
18904                "A multi buffer was expected to active after adding"
18905            );
18906            active_item.item_id()
18907        })
18908        .unwrap();
18909    cx.executor().run_until_parked();
18910
18911    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18912        editor.change_selections(
18913            SelectionEffects::scroll(Autoscroll::Next),
18914            window,
18915            cx,
18916            |s| s.select_ranges(Some(1..2)),
18917        );
18918        editor.open_excerpts(&OpenExcerpts, window, cx);
18919    });
18920    cx.executor().run_until_parked();
18921    let first_item_id = workspace
18922        .update(cx, |workspace, window, cx| {
18923            let active_item = workspace
18924                .active_item(cx)
18925                .expect("should have an active item after navigating into the 1st buffer");
18926            let first_item_id = active_item.item_id();
18927            assert_ne!(
18928                first_item_id, multibuffer_item_id,
18929                "Should navigate into the 1st buffer and activate it"
18930            );
18931            assert_eq!(
18932                active_item.buffer_kind(cx),
18933                ItemBufferKind::Singleton,
18934                "New active item should be a singleton buffer"
18935            );
18936            assert_eq!(
18937                active_item
18938                    .act_as::<Editor>(cx)
18939                    .expect("should have navigated into an editor for the 1st buffer")
18940                    .read(cx)
18941                    .text(cx),
18942                sample_text_1
18943            );
18944
18945            workspace
18946                .go_back(workspace.active_pane().downgrade(), window, cx)
18947                .detach_and_log_err(cx);
18948
18949            first_item_id
18950        })
18951        .unwrap();
18952    cx.executor().run_until_parked();
18953    workspace
18954        .update(cx, |workspace, _, cx| {
18955            let active_item = workspace
18956                .active_item(cx)
18957                .expect("should have an active item after navigating back");
18958            assert_eq!(
18959                active_item.item_id(),
18960                multibuffer_item_id,
18961                "Should navigate back to the multi buffer"
18962            );
18963            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
18964        })
18965        .unwrap();
18966
18967    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18968        editor.change_selections(
18969            SelectionEffects::scroll(Autoscroll::Next),
18970            window,
18971            cx,
18972            |s| s.select_ranges(Some(39..40)),
18973        );
18974        editor.open_excerpts(&OpenExcerpts, window, cx);
18975    });
18976    cx.executor().run_until_parked();
18977    let second_item_id = workspace
18978        .update(cx, |workspace, window, cx| {
18979            let active_item = workspace
18980                .active_item(cx)
18981                .expect("should have an active item after navigating into the 2nd buffer");
18982            let second_item_id = active_item.item_id();
18983            assert_ne!(
18984                second_item_id, multibuffer_item_id,
18985                "Should navigate away from the multibuffer"
18986            );
18987            assert_ne!(
18988                second_item_id, first_item_id,
18989                "Should navigate into the 2nd buffer and activate it"
18990            );
18991            assert_eq!(
18992                active_item.buffer_kind(cx),
18993                ItemBufferKind::Singleton,
18994                "New active item should be a singleton buffer"
18995            );
18996            assert_eq!(
18997                active_item
18998                    .act_as::<Editor>(cx)
18999                    .expect("should have navigated into an editor")
19000                    .read(cx)
19001                    .text(cx),
19002                sample_text_2
19003            );
19004
19005            workspace
19006                .go_back(workspace.active_pane().downgrade(), window, cx)
19007                .detach_and_log_err(cx);
19008
19009            second_item_id
19010        })
19011        .unwrap();
19012    cx.executor().run_until_parked();
19013    workspace
19014        .update(cx, |workspace, _, cx| {
19015            let active_item = workspace
19016                .active_item(cx)
19017                .expect("should have an active item after navigating back from the 2nd buffer");
19018            assert_eq!(
19019                active_item.item_id(),
19020                multibuffer_item_id,
19021                "Should navigate back from the 2nd buffer to the multi buffer"
19022            );
19023            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19024        })
19025        .unwrap();
19026
19027    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19028        editor.change_selections(
19029            SelectionEffects::scroll(Autoscroll::Next),
19030            window,
19031            cx,
19032            |s| s.select_ranges(Some(70..70)),
19033        );
19034        editor.open_excerpts(&OpenExcerpts, window, cx);
19035    });
19036    cx.executor().run_until_parked();
19037    workspace
19038        .update(cx, |workspace, window, cx| {
19039            let active_item = workspace
19040                .active_item(cx)
19041                .expect("should have an active item after navigating into the 3rd buffer");
19042            let third_item_id = active_item.item_id();
19043            assert_ne!(
19044                third_item_id, multibuffer_item_id,
19045                "Should navigate into the 3rd buffer and activate it"
19046            );
19047            assert_ne!(third_item_id, first_item_id);
19048            assert_ne!(third_item_id, second_item_id);
19049            assert_eq!(
19050                active_item.buffer_kind(cx),
19051                ItemBufferKind::Singleton,
19052                "New active item should be a singleton buffer"
19053            );
19054            assert_eq!(
19055                active_item
19056                    .act_as::<Editor>(cx)
19057                    .expect("should have navigated into an editor")
19058                    .read(cx)
19059                    .text(cx),
19060                sample_text_3
19061            );
19062
19063            workspace
19064                .go_back(workspace.active_pane().downgrade(), window, cx)
19065                .detach_and_log_err(cx);
19066        })
19067        .unwrap();
19068    cx.executor().run_until_parked();
19069    workspace
19070        .update(cx, |workspace, _, cx| {
19071            let active_item = workspace
19072                .active_item(cx)
19073                .expect("should have an active item after navigating back from the 3rd buffer");
19074            assert_eq!(
19075                active_item.item_id(),
19076                multibuffer_item_id,
19077                "Should navigate back from the 3rd buffer to the multi buffer"
19078            );
19079            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19080        })
19081        .unwrap();
19082}
19083
19084#[gpui::test]
19085async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19086    init_test(cx, |_| {});
19087
19088    let mut cx = EditorTestContext::new(cx).await;
19089
19090    let diff_base = r#"
19091        use some::mod;
19092
19093        const A: u32 = 42;
19094
19095        fn main() {
19096            println!("hello");
19097
19098            println!("world");
19099        }
19100        "#
19101    .unindent();
19102
19103    cx.set_state(
19104        &r#"
19105        use some::modified;
19106
19107        ˇ
19108        fn main() {
19109            println!("hello there");
19110
19111            println!("around the");
19112            println!("world");
19113        }
19114        "#
19115        .unindent(),
19116    );
19117
19118    cx.set_head_text(&diff_base);
19119    executor.run_until_parked();
19120
19121    cx.update_editor(|editor, window, cx| {
19122        editor.go_to_next_hunk(&GoToHunk, window, cx);
19123        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19124    });
19125    executor.run_until_parked();
19126    cx.assert_state_with_diff(
19127        r#"
19128          use some::modified;
19129
19130
19131          fn main() {
19132        -     println!("hello");
19133        + ˇ    println!("hello there");
19134
19135              println!("around the");
19136              println!("world");
19137          }
19138        "#
19139        .unindent(),
19140    );
19141
19142    cx.update_editor(|editor, window, cx| {
19143        for _ in 0..2 {
19144            editor.go_to_next_hunk(&GoToHunk, window, cx);
19145            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19146        }
19147    });
19148    executor.run_until_parked();
19149    cx.assert_state_with_diff(
19150        r#"
19151        - use some::mod;
19152        + ˇuse some::modified;
19153
19154
19155          fn main() {
19156        -     println!("hello");
19157        +     println!("hello there");
19158
19159        +     println!("around the");
19160              println!("world");
19161          }
19162        "#
19163        .unindent(),
19164    );
19165
19166    cx.update_editor(|editor, window, cx| {
19167        editor.go_to_next_hunk(&GoToHunk, window, cx);
19168        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19169    });
19170    executor.run_until_parked();
19171    cx.assert_state_with_diff(
19172        r#"
19173        - use some::mod;
19174        + use some::modified;
19175
19176        - const A: u32 = 42;
19177          ˇ
19178          fn main() {
19179        -     println!("hello");
19180        +     println!("hello there");
19181
19182        +     println!("around the");
19183              println!("world");
19184          }
19185        "#
19186        .unindent(),
19187    );
19188
19189    cx.update_editor(|editor, window, cx| {
19190        editor.cancel(&Cancel, window, cx);
19191    });
19192
19193    cx.assert_state_with_diff(
19194        r#"
19195          use some::modified;
19196
19197          ˇ
19198          fn main() {
19199              println!("hello there");
19200
19201              println!("around the");
19202              println!("world");
19203          }
19204        "#
19205        .unindent(),
19206    );
19207}
19208
19209#[gpui::test]
19210async fn test_diff_base_change_with_expanded_diff_hunks(
19211    executor: BackgroundExecutor,
19212    cx: &mut TestAppContext,
19213) {
19214    init_test(cx, |_| {});
19215
19216    let mut cx = EditorTestContext::new(cx).await;
19217
19218    let diff_base = r#"
19219        use some::mod1;
19220        use some::mod2;
19221
19222        const A: u32 = 42;
19223        const B: u32 = 42;
19224        const C: u32 = 42;
19225
19226        fn main() {
19227            println!("hello");
19228
19229            println!("world");
19230        }
19231        "#
19232    .unindent();
19233
19234    cx.set_state(
19235        &r#"
19236        use some::mod2;
19237
19238        const A: u32 = 42;
19239        const C: u32 = 42;
19240
19241        fn main(ˇ) {
19242            //println!("hello");
19243
19244            println!("world");
19245            //
19246            //
19247        }
19248        "#
19249        .unindent(),
19250    );
19251
19252    cx.set_head_text(&diff_base);
19253    executor.run_until_parked();
19254
19255    cx.update_editor(|editor, window, cx| {
19256        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19257    });
19258    executor.run_until_parked();
19259    cx.assert_state_with_diff(
19260        r#"
19261        - use some::mod1;
19262          use some::mod2;
19263
19264          const A: u32 = 42;
19265        - const B: u32 = 42;
19266          const C: u32 = 42;
19267
19268          fn main(ˇ) {
19269        -     println!("hello");
19270        +     //println!("hello");
19271
19272              println!("world");
19273        +     //
19274        +     //
19275          }
19276        "#
19277        .unindent(),
19278    );
19279
19280    cx.set_head_text("new diff base!");
19281    executor.run_until_parked();
19282    cx.assert_state_with_diff(
19283        r#"
19284        - new diff base!
19285        + use some::mod2;
19286        +
19287        + const A: u32 = 42;
19288        + const C: u32 = 42;
19289        +
19290        + fn main(ˇ) {
19291        +     //println!("hello");
19292        +
19293        +     println!("world");
19294        +     //
19295        +     //
19296        + }
19297        "#
19298        .unindent(),
19299    );
19300}
19301
19302#[gpui::test]
19303async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19304    init_test(cx, |_| {});
19305
19306    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19307    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19308    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19309    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19310    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19311    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19312
19313    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19314    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19315    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19316
19317    let multi_buffer = cx.new(|cx| {
19318        let mut multibuffer = MultiBuffer::new(ReadWrite);
19319        multibuffer.push_excerpts(
19320            buffer_1.clone(),
19321            [
19322                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19323                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19324                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19325            ],
19326            cx,
19327        );
19328        multibuffer.push_excerpts(
19329            buffer_2.clone(),
19330            [
19331                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19332                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19333                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19334            ],
19335            cx,
19336        );
19337        multibuffer.push_excerpts(
19338            buffer_3.clone(),
19339            [
19340                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19341                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19342                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19343            ],
19344            cx,
19345        );
19346        multibuffer
19347    });
19348
19349    let editor =
19350        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19351    editor
19352        .update(cx, |editor, _window, cx| {
19353            for (buffer, diff_base) in [
19354                (buffer_1.clone(), file_1_old),
19355                (buffer_2.clone(), file_2_old),
19356                (buffer_3.clone(), file_3_old),
19357            ] {
19358                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19359                editor
19360                    .buffer
19361                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19362            }
19363        })
19364        .unwrap();
19365
19366    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19367    cx.run_until_parked();
19368
19369    cx.assert_editor_state(
19370        &"
19371            ˇaaa
19372            ccc
19373            ddd
19374
19375            ggg
19376            hhh
19377
19378
19379            lll
19380            mmm
19381            NNN
19382
19383            qqq
19384            rrr
19385
19386            uuu
19387            111
19388            222
19389            333
19390
19391            666
19392            777
19393
19394            000
19395            !!!"
19396        .unindent(),
19397    );
19398
19399    cx.update_editor(|editor, window, cx| {
19400        editor.select_all(&SelectAll, window, cx);
19401        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19402    });
19403    cx.executor().run_until_parked();
19404
19405    cx.assert_state_with_diff(
19406        "
19407            «aaa
19408          - bbb
19409            ccc
19410            ddd
19411
19412            ggg
19413            hhh
19414
19415
19416            lll
19417            mmm
19418          - nnn
19419          + NNN
19420
19421            qqq
19422            rrr
19423
19424            uuu
19425            111
19426            222
19427            333
19428
19429          + 666
19430            777
19431
19432            000
19433            !!!ˇ»"
19434            .unindent(),
19435    );
19436}
19437
19438#[gpui::test]
19439async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19440    init_test(cx, |_| {});
19441
19442    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19443    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19444
19445    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19446    let multi_buffer = cx.new(|cx| {
19447        let mut multibuffer = MultiBuffer::new(ReadWrite);
19448        multibuffer.push_excerpts(
19449            buffer.clone(),
19450            [
19451                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19452                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19453                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19454            ],
19455            cx,
19456        );
19457        multibuffer
19458    });
19459
19460    let editor =
19461        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19462    editor
19463        .update(cx, |editor, _window, cx| {
19464            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19465            editor
19466                .buffer
19467                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19468        })
19469        .unwrap();
19470
19471    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19472    cx.run_until_parked();
19473
19474    cx.update_editor(|editor, window, cx| {
19475        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19476    });
19477    cx.executor().run_until_parked();
19478
19479    // When the start of a hunk coincides with the start of its excerpt,
19480    // the hunk is expanded. When the start of a hunk is earlier than
19481    // the start of its excerpt, the hunk is not expanded.
19482    cx.assert_state_with_diff(
19483        "
19484            ˇaaa
19485          - bbb
19486          + BBB
19487
19488          - ddd
19489          - eee
19490          + DDD
19491          + EEE
19492            fff
19493
19494            iii
19495        "
19496        .unindent(),
19497    );
19498}
19499
19500#[gpui::test]
19501async fn test_edits_around_expanded_insertion_hunks(
19502    executor: BackgroundExecutor,
19503    cx: &mut TestAppContext,
19504) {
19505    init_test(cx, |_| {});
19506
19507    let mut cx = EditorTestContext::new(cx).await;
19508
19509    let diff_base = r#"
19510        use some::mod1;
19511        use some::mod2;
19512
19513        const A: u32 = 42;
19514
19515        fn main() {
19516            println!("hello");
19517
19518            println!("world");
19519        }
19520        "#
19521    .unindent();
19522    executor.run_until_parked();
19523    cx.set_state(
19524        &r#"
19525        use some::mod1;
19526        use some::mod2;
19527
19528        const A: u32 = 42;
19529        const B: u32 = 42;
19530        const C: u32 = 42;
19531        ˇ
19532
19533        fn main() {
19534            println!("hello");
19535
19536            println!("world");
19537        }
19538        "#
19539        .unindent(),
19540    );
19541
19542    cx.set_head_text(&diff_base);
19543    executor.run_until_parked();
19544
19545    cx.update_editor(|editor, window, cx| {
19546        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19547    });
19548    executor.run_until_parked();
19549
19550    cx.assert_state_with_diff(
19551        r#"
19552        use some::mod1;
19553        use some::mod2;
19554
19555        const A: u32 = 42;
19556      + const B: u32 = 42;
19557      + const C: u32 = 42;
19558      + ˇ
19559
19560        fn main() {
19561            println!("hello");
19562
19563            println!("world");
19564        }
19565      "#
19566        .unindent(),
19567    );
19568
19569    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19570    executor.run_until_parked();
19571
19572    cx.assert_state_with_diff(
19573        r#"
19574        use some::mod1;
19575        use some::mod2;
19576
19577        const A: u32 = 42;
19578      + const B: u32 = 42;
19579      + const C: u32 = 42;
19580      + const D: u32 = 42;
19581      + ˇ
19582
19583        fn main() {
19584            println!("hello");
19585
19586            println!("world");
19587        }
19588      "#
19589        .unindent(),
19590    );
19591
19592    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19593    executor.run_until_parked();
19594
19595    cx.assert_state_with_diff(
19596        r#"
19597        use some::mod1;
19598        use some::mod2;
19599
19600        const A: u32 = 42;
19601      + const B: u32 = 42;
19602      + const C: u32 = 42;
19603      + const D: u32 = 42;
19604      + const E: u32 = 42;
19605      + ˇ
19606
19607        fn main() {
19608            println!("hello");
19609
19610            println!("world");
19611        }
19612      "#
19613        .unindent(),
19614    );
19615
19616    cx.update_editor(|editor, window, cx| {
19617        editor.delete_line(&DeleteLine, window, cx);
19618    });
19619    executor.run_until_parked();
19620
19621    cx.assert_state_with_diff(
19622        r#"
19623        use some::mod1;
19624        use some::mod2;
19625
19626        const A: u32 = 42;
19627      + const B: u32 = 42;
19628      + const C: u32 = 42;
19629      + const D: u32 = 42;
19630      + const E: u32 = 42;
19631        ˇ
19632        fn main() {
19633            println!("hello");
19634
19635            println!("world");
19636        }
19637      "#
19638        .unindent(),
19639    );
19640
19641    cx.update_editor(|editor, window, cx| {
19642        editor.move_up(&MoveUp, window, cx);
19643        editor.delete_line(&DeleteLine, window, cx);
19644        editor.move_up(&MoveUp, window, cx);
19645        editor.delete_line(&DeleteLine, window, cx);
19646        editor.move_up(&MoveUp, window, cx);
19647        editor.delete_line(&DeleteLine, window, cx);
19648    });
19649    executor.run_until_parked();
19650    cx.assert_state_with_diff(
19651        r#"
19652        use some::mod1;
19653        use some::mod2;
19654
19655        const A: u32 = 42;
19656      + const B: u32 = 42;
19657        ˇ
19658        fn main() {
19659            println!("hello");
19660
19661            println!("world");
19662        }
19663      "#
19664        .unindent(),
19665    );
19666
19667    cx.update_editor(|editor, window, cx| {
19668        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19669        editor.delete_line(&DeleteLine, window, cx);
19670    });
19671    executor.run_until_parked();
19672    cx.assert_state_with_diff(
19673        r#"
19674        ˇ
19675        fn main() {
19676            println!("hello");
19677
19678            println!("world");
19679        }
19680      "#
19681        .unindent(),
19682    );
19683}
19684
19685#[gpui::test]
19686async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19687    init_test(cx, |_| {});
19688
19689    let mut cx = EditorTestContext::new(cx).await;
19690    cx.set_head_text(indoc! { "
19691        one
19692        two
19693        three
19694        four
19695        five
19696        "
19697    });
19698    cx.set_state(indoc! { "
19699        one
19700        ˇthree
19701        five
19702    "});
19703    cx.run_until_parked();
19704    cx.update_editor(|editor, window, cx| {
19705        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19706    });
19707    cx.assert_state_with_diff(
19708        indoc! { "
19709        one
19710      - two
19711        ˇthree
19712      - four
19713        five
19714    "}
19715        .to_string(),
19716    );
19717    cx.update_editor(|editor, window, cx| {
19718        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19719    });
19720
19721    cx.assert_state_with_diff(
19722        indoc! { "
19723        one
19724        ˇthree
19725        five
19726    "}
19727        .to_string(),
19728    );
19729
19730    cx.set_state(indoc! { "
19731        one
19732        ˇTWO
19733        three
19734        four
19735        five
19736    "});
19737    cx.run_until_parked();
19738    cx.update_editor(|editor, window, cx| {
19739        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19740    });
19741
19742    cx.assert_state_with_diff(
19743        indoc! { "
19744            one
19745          - two
19746          + ˇTWO
19747            three
19748            four
19749            five
19750        "}
19751        .to_string(),
19752    );
19753    cx.update_editor(|editor, window, cx| {
19754        editor.move_up(&Default::default(), window, cx);
19755        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19756    });
19757    cx.assert_state_with_diff(
19758        indoc! { "
19759            one
19760            ˇTWO
19761            three
19762            four
19763            five
19764        "}
19765        .to_string(),
19766    );
19767}
19768
19769#[gpui::test]
19770async fn test_edits_around_expanded_deletion_hunks(
19771    executor: BackgroundExecutor,
19772    cx: &mut TestAppContext,
19773) {
19774    init_test(cx, |_| {});
19775
19776    let mut cx = EditorTestContext::new(cx).await;
19777
19778    let diff_base = r#"
19779        use some::mod1;
19780        use some::mod2;
19781
19782        const A: u32 = 42;
19783        const B: u32 = 42;
19784        const C: u32 = 42;
19785
19786
19787        fn main() {
19788            println!("hello");
19789
19790            println!("world");
19791        }
19792    "#
19793    .unindent();
19794    executor.run_until_parked();
19795    cx.set_state(
19796        &r#"
19797        use some::mod1;
19798        use some::mod2;
19799
19800        ˇconst B: u32 = 42;
19801        const C: u32 = 42;
19802
19803
19804        fn main() {
19805            println!("hello");
19806
19807            println!("world");
19808        }
19809        "#
19810        .unindent(),
19811    );
19812
19813    cx.set_head_text(&diff_base);
19814    executor.run_until_parked();
19815
19816    cx.update_editor(|editor, window, cx| {
19817        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19818    });
19819    executor.run_until_parked();
19820
19821    cx.assert_state_with_diff(
19822        r#"
19823        use some::mod1;
19824        use some::mod2;
19825
19826      - const A: u32 = 42;
19827        ˇconst B: u32 = 42;
19828        const C: u32 = 42;
19829
19830
19831        fn main() {
19832            println!("hello");
19833
19834            println!("world");
19835        }
19836      "#
19837        .unindent(),
19838    );
19839
19840    cx.update_editor(|editor, window, cx| {
19841        editor.delete_line(&DeleteLine, window, cx);
19842    });
19843    executor.run_until_parked();
19844    cx.assert_state_with_diff(
19845        r#"
19846        use some::mod1;
19847        use some::mod2;
19848
19849      - const A: u32 = 42;
19850      - const B: u32 = 42;
19851        ˇconst C: u32 = 42;
19852
19853
19854        fn main() {
19855            println!("hello");
19856
19857            println!("world");
19858        }
19859      "#
19860        .unindent(),
19861    );
19862
19863    cx.update_editor(|editor, window, cx| {
19864        editor.delete_line(&DeleteLine, window, cx);
19865    });
19866    executor.run_until_parked();
19867    cx.assert_state_with_diff(
19868        r#"
19869        use some::mod1;
19870        use some::mod2;
19871
19872      - const A: u32 = 42;
19873      - const B: u32 = 42;
19874      - const C: u32 = 42;
19875        ˇ
19876
19877        fn main() {
19878            println!("hello");
19879
19880            println!("world");
19881        }
19882      "#
19883        .unindent(),
19884    );
19885
19886    cx.update_editor(|editor, window, cx| {
19887        editor.handle_input("replacement", window, cx);
19888    });
19889    executor.run_until_parked();
19890    cx.assert_state_with_diff(
19891        r#"
19892        use some::mod1;
19893        use some::mod2;
19894
19895      - const A: u32 = 42;
19896      - const B: u32 = 42;
19897      - const C: u32 = 42;
19898      -
19899      + replacementˇ
19900
19901        fn main() {
19902            println!("hello");
19903
19904            println!("world");
19905        }
19906      "#
19907        .unindent(),
19908    );
19909}
19910
19911#[gpui::test]
19912async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19913    init_test(cx, |_| {});
19914
19915    let mut cx = EditorTestContext::new(cx).await;
19916
19917    let base_text = r#"
19918        one
19919        two
19920        three
19921        four
19922        five
19923    "#
19924    .unindent();
19925    executor.run_until_parked();
19926    cx.set_state(
19927        &r#"
19928        one
19929        two
19930        fˇour
19931        five
19932        "#
19933        .unindent(),
19934    );
19935
19936    cx.set_head_text(&base_text);
19937    executor.run_until_parked();
19938
19939    cx.update_editor(|editor, window, cx| {
19940        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19941    });
19942    executor.run_until_parked();
19943
19944    cx.assert_state_with_diff(
19945        r#"
19946          one
19947          two
19948        - three
19949          fˇour
19950          five
19951        "#
19952        .unindent(),
19953    );
19954
19955    cx.update_editor(|editor, window, cx| {
19956        editor.backspace(&Backspace, window, cx);
19957        editor.backspace(&Backspace, window, cx);
19958    });
19959    executor.run_until_parked();
19960    cx.assert_state_with_diff(
19961        r#"
19962          one
19963          two
19964        - threeˇ
19965        - four
19966        + our
19967          five
19968        "#
19969        .unindent(),
19970    );
19971}
19972
19973#[gpui::test]
19974async fn test_edit_after_expanded_modification_hunk(
19975    executor: BackgroundExecutor,
19976    cx: &mut TestAppContext,
19977) {
19978    init_test(cx, |_| {});
19979
19980    let mut cx = EditorTestContext::new(cx).await;
19981
19982    let diff_base = r#"
19983        use some::mod1;
19984        use some::mod2;
19985
19986        const A: u32 = 42;
19987        const B: u32 = 42;
19988        const C: u32 = 42;
19989        const D: u32 = 42;
19990
19991
19992        fn main() {
19993            println!("hello");
19994
19995            println!("world");
19996        }"#
19997    .unindent();
19998
19999    cx.set_state(
20000        &r#"
20001        use some::mod1;
20002        use some::mod2;
20003
20004        const A: u32 = 42;
20005        const B: u32 = 42;
20006        const C: u32 = 43ˇ
20007        const D: u32 = 42;
20008
20009
20010        fn main() {
20011            println!("hello");
20012
20013            println!("world");
20014        }"#
20015        .unindent(),
20016    );
20017
20018    cx.set_head_text(&diff_base);
20019    executor.run_until_parked();
20020    cx.update_editor(|editor, window, cx| {
20021        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20022    });
20023    executor.run_until_parked();
20024
20025    cx.assert_state_with_diff(
20026        r#"
20027        use some::mod1;
20028        use some::mod2;
20029
20030        const A: u32 = 42;
20031        const B: u32 = 42;
20032      - const C: u32 = 42;
20033      + const C: u32 = 43ˇ
20034        const D: u32 = 42;
20035
20036
20037        fn main() {
20038            println!("hello");
20039
20040            println!("world");
20041        }"#
20042        .unindent(),
20043    );
20044
20045    cx.update_editor(|editor, window, cx| {
20046        editor.handle_input("\nnew_line\n", window, cx);
20047    });
20048    executor.run_until_parked();
20049
20050    cx.assert_state_with_diff(
20051        r#"
20052        use some::mod1;
20053        use some::mod2;
20054
20055        const A: u32 = 42;
20056        const B: u32 = 42;
20057      - const C: u32 = 42;
20058      + const C: u32 = 43
20059      + new_line
20060      + ˇ
20061        const D: u32 = 42;
20062
20063
20064        fn main() {
20065            println!("hello");
20066
20067            println!("world");
20068        }"#
20069        .unindent(),
20070    );
20071}
20072
20073#[gpui::test]
20074async fn test_stage_and_unstage_added_file_hunk(
20075    executor: BackgroundExecutor,
20076    cx: &mut TestAppContext,
20077) {
20078    init_test(cx, |_| {});
20079
20080    let mut cx = EditorTestContext::new(cx).await;
20081    cx.update_editor(|editor, _, cx| {
20082        editor.set_expand_all_diff_hunks(cx);
20083    });
20084
20085    let working_copy = r#"
20086            ˇfn main() {
20087                println!("hello, world!");
20088            }
20089        "#
20090    .unindent();
20091
20092    cx.set_state(&working_copy);
20093    executor.run_until_parked();
20094
20095    cx.assert_state_with_diff(
20096        r#"
20097            + ˇfn main() {
20098            +     println!("hello, world!");
20099            + }
20100        "#
20101        .unindent(),
20102    );
20103    cx.assert_index_text(None);
20104
20105    cx.update_editor(|editor, window, cx| {
20106        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20107    });
20108    executor.run_until_parked();
20109    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20110    cx.assert_state_with_diff(
20111        r#"
20112            + ˇfn main() {
20113            +     println!("hello, world!");
20114            + }
20115        "#
20116        .unindent(),
20117    );
20118
20119    cx.update_editor(|editor, window, cx| {
20120        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20121    });
20122    executor.run_until_parked();
20123    cx.assert_index_text(None);
20124}
20125
20126async fn setup_indent_guides_editor(
20127    text: &str,
20128    cx: &mut TestAppContext,
20129) -> (BufferId, EditorTestContext) {
20130    init_test(cx, |_| {});
20131
20132    let mut cx = EditorTestContext::new(cx).await;
20133
20134    let buffer_id = cx.update_editor(|editor, window, cx| {
20135        editor.set_text(text, window, cx);
20136        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20137
20138        buffer_ids[0]
20139    });
20140
20141    (buffer_id, cx)
20142}
20143
20144fn assert_indent_guides(
20145    range: Range<u32>,
20146    expected: Vec<IndentGuide>,
20147    active_indices: Option<Vec<usize>>,
20148    cx: &mut EditorTestContext,
20149) {
20150    let indent_guides = cx.update_editor(|editor, window, cx| {
20151        let snapshot = editor.snapshot(window, cx).display_snapshot;
20152        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20153            editor,
20154            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20155            true,
20156            &snapshot,
20157            cx,
20158        );
20159
20160        indent_guides.sort_by(|a, b| {
20161            a.depth.cmp(&b.depth).then(
20162                a.start_row
20163                    .cmp(&b.start_row)
20164                    .then(a.end_row.cmp(&b.end_row)),
20165            )
20166        });
20167        indent_guides
20168    });
20169
20170    if let Some(expected) = active_indices {
20171        let active_indices = cx.update_editor(|editor, window, cx| {
20172            let snapshot = editor.snapshot(window, cx).display_snapshot;
20173            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20174        });
20175
20176        assert_eq!(
20177            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20178            expected,
20179            "Active indent guide indices do not match"
20180        );
20181    }
20182
20183    assert_eq!(indent_guides, expected, "Indent guides do not match");
20184}
20185
20186fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20187    IndentGuide {
20188        buffer_id,
20189        start_row: MultiBufferRow(start_row),
20190        end_row: MultiBufferRow(end_row),
20191        depth,
20192        tab_size: 4,
20193        settings: IndentGuideSettings {
20194            enabled: true,
20195            line_width: 1,
20196            active_line_width: 1,
20197            coloring: IndentGuideColoring::default(),
20198            background_coloring: IndentGuideBackgroundColoring::default(),
20199        },
20200    }
20201}
20202
20203#[gpui::test]
20204async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20205    let (buffer_id, mut cx) = setup_indent_guides_editor(
20206        &"
20207        fn main() {
20208            let a = 1;
20209        }"
20210        .unindent(),
20211        cx,
20212    )
20213    .await;
20214
20215    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20216}
20217
20218#[gpui::test]
20219async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20220    let (buffer_id, mut cx) = setup_indent_guides_editor(
20221        &"
20222        fn main() {
20223            let a = 1;
20224            let b = 2;
20225        }"
20226        .unindent(),
20227        cx,
20228    )
20229    .await;
20230
20231    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20232}
20233
20234#[gpui::test]
20235async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20236    let (buffer_id, mut cx) = setup_indent_guides_editor(
20237        &"
20238        fn main() {
20239            let a = 1;
20240            if a == 3 {
20241                let b = 2;
20242            } else {
20243                let c = 3;
20244            }
20245        }"
20246        .unindent(),
20247        cx,
20248    )
20249    .await;
20250
20251    assert_indent_guides(
20252        0..8,
20253        vec![
20254            indent_guide(buffer_id, 1, 6, 0),
20255            indent_guide(buffer_id, 3, 3, 1),
20256            indent_guide(buffer_id, 5, 5, 1),
20257        ],
20258        None,
20259        &mut cx,
20260    );
20261}
20262
20263#[gpui::test]
20264async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20265    let (buffer_id, mut cx) = setup_indent_guides_editor(
20266        &"
20267        fn main() {
20268            let a = 1;
20269                let b = 2;
20270            let c = 3;
20271        }"
20272        .unindent(),
20273        cx,
20274    )
20275    .await;
20276
20277    assert_indent_guides(
20278        0..5,
20279        vec![
20280            indent_guide(buffer_id, 1, 3, 0),
20281            indent_guide(buffer_id, 2, 2, 1),
20282        ],
20283        None,
20284        &mut cx,
20285    );
20286}
20287
20288#[gpui::test]
20289async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20290    let (buffer_id, mut cx) = setup_indent_guides_editor(
20291        &"
20292        fn main() {
20293            let a = 1;
20294
20295            let c = 3;
20296        }"
20297        .unindent(),
20298        cx,
20299    )
20300    .await;
20301
20302    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20303}
20304
20305#[gpui::test]
20306async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20307    let (buffer_id, mut cx) = setup_indent_guides_editor(
20308        &"
20309        fn main() {
20310            let a = 1;
20311
20312            let c = 3;
20313
20314            if a == 3 {
20315                let b = 2;
20316            } else {
20317                let c = 3;
20318            }
20319        }"
20320        .unindent(),
20321        cx,
20322    )
20323    .await;
20324
20325    assert_indent_guides(
20326        0..11,
20327        vec![
20328            indent_guide(buffer_id, 1, 9, 0),
20329            indent_guide(buffer_id, 6, 6, 1),
20330            indent_guide(buffer_id, 8, 8, 1),
20331        ],
20332        None,
20333        &mut cx,
20334    );
20335}
20336
20337#[gpui::test]
20338async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20339    let (buffer_id, mut cx) = setup_indent_guides_editor(
20340        &"
20341        fn main() {
20342            let a = 1;
20343
20344            let c = 3;
20345
20346            if a == 3 {
20347                let b = 2;
20348            } else {
20349                let c = 3;
20350            }
20351        }"
20352        .unindent(),
20353        cx,
20354    )
20355    .await;
20356
20357    assert_indent_guides(
20358        1..11,
20359        vec![
20360            indent_guide(buffer_id, 1, 9, 0),
20361            indent_guide(buffer_id, 6, 6, 1),
20362            indent_guide(buffer_id, 8, 8, 1),
20363        ],
20364        None,
20365        &mut cx,
20366    );
20367}
20368
20369#[gpui::test]
20370async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20371    let (buffer_id, mut cx) = setup_indent_guides_editor(
20372        &"
20373        fn main() {
20374            let a = 1;
20375
20376            let c = 3;
20377
20378            if a == 3 {
20379                let b = 2;
20380            } else {
20381                let c = 3;
20382            }
20383        }"
20384        .unindent(),
20385        cx,
20386    )
20387    .await;
20388
20389    assert_indent_guides(
20390        1..10,
20391        vec![
20392            indent_guide(buffer_id, 1, 9, 0),
20393            indent_guide(buffer_id, 6, 6, 1),
20394            indent_guide(buffer_id, 8, 8, 1),
20395        ],
20396        None,
20397        &mut cx,
20398    );
20399}
20400
20401#[gpui::test]
20402async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20403    let (buffer_id, mut cx) = setup_indent_guides_editor(
20404        &"
20405        fn main() {
20406            if a {
20407                b(
20408                    c,
20409                    d,
20410                )
20411            } else {
20412                e(
20413                    f
20414                )
20415            }
20416        }"
20417        .unindent(),
20418        cx,
20419    )
20420    .await;
20421
20422    assert_indent_guides(
20423        0..11,
20424        vec![
20425            indent_guide(buffer_id, 1, 10, 0),
20426            indent_guide(buffer_id, 2, 5, 1),
20427            indent_guide(buffer_id, 7, 9, 1),
20428            indent_guide(buffer_id, 3, 4, 2),
20429            indent_guide(buffer_id, 8, 8, 2),
20430        ],
20431        None,
20432        &mut cx,
20433    );
20434
20435    cx.update_editor(|editor, window, cx| {
20436        editor.fold_at(MultiBufferRow(2), window, cx);
20437        assert_eq!(
20438            editor.display_text(cx),
20439            "
20440            fn main() {
20441                if a {
20442                    b(⋯
20443                    )
20444                } else {
20445                    e(
20446                        f
20447                    )
20448                }
20449            }"
20450            .unindent()
20451        );
20452    });
20453
20454    assert_indent_guides(
20455        0..11,
20456        vec![
20457            indent_guide(buffer_id, 1, 10, 0),
20458            indent_guide(buffer_id, 2, 5, 1),
20459            indent_guide(buffer_id, 7, 9, 1),
20460            indent_guide(buffer_id, 8, 8, 2),
20461        ],
20462        None,
20463        &mut cx,
20464    );
20465}
20466
20467#[gpui::test]
20468async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20469    let (buffer_id, mut cx) = setup_indent_guides_editor(
20470        &"
20471        block1
20472            block2
20473                block3
20474                    block4
20475            block2
20476        block1
20477        block1"
20478            .unindent(),
20479        cx,
20480    )
20481    .await;
20482
20483    assert_indent_guides(
20484        1..10,
20485        vec![
20486            indent_guide(buffer_id, 1, 4, 0),
20487            indent_guide(buffer_id, 2, 3, 1),
20488            indent_guide(buffer_id, 3, 3, 2),
20489        ],
20490        None,
20491        &mut cx,
20492    );
20493}
20494
20495#[gpui::test]
20496async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20497    let (buffer_id, mut cx) = setup_indent_guides_editor(
20498        &"
20499        block1
20500            block2
20501                block3
20502
20503        block1
20504        block1"
20505            .unindent(),
20506        cx,
20507    )
20508    .await;
20509
20510    assert_indent_guides(
20511        0..6,
20512        vec![
20513            indent_guide(buffer_id, 1, 2, 0),
20514            indent_guide(buffer_id, 2, 2, 1),
20515        ],
20516        None,
20517        &mut cx,
20518    );
20519}
20520
20521#[gpui::test]
20522async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20523    let (buffer_id, mut cx) = setup_indent_guides_editor(
20524        &"
20525        function component() {
20526        \treturn (
20527        \t\t\t
20528        \t\t<div>
20529        \t\t\t<abc></abc>
20530        \t\t</div>
20531        \t)
20532        }"
20533        .unindent(),
20534        cx,
20535    )
20536    .await;
20537
20538    assert_indent_guides(
20539        0..8,
20540        vec![
20541            indent_guide(buffer_id, 1, 6, 0),
20542            indent_guide(buffer_id, 2, 5, 1),
20543            indent_guide(buffer_id, 4, 4, 2),
20544        ],
20545        None,
20546        &mut cx,
20547    );
20548}
20549
20550#[gpui::test]
20551async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20552    let (buffer_id, mut cx) = setup_indent_guides_editor(
20553        &"
20554        function component() {
20555        \treturn (
20556        \t
20557        \t\t<div>
20558        \t\t\t<abc></abc>
20559        \t\t</div>
20560        \t)
20561        }"
20562        .unindent(),
20563        cx,
20564    )
20565    .await;
20566
20567    assert_indent_guides(
20568        0..8,
20569        vec![
20570            indent_guide(buffer_id, 1, 6, 0),
20571            indent_guide(buffer_id, 2, 5, 1),
20572            indent_guide(buffer_id, 4, 4, 2),
20573        ],
20574        None,
20575        &mut cx,
20576    );
20577}
20578
20579#[gpui::test]
20580async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20581    let (buffer_id, mut cx) = setup_indent_guides_editor(
20582        &"
20583        block1
20584
20585
20586
20587            block2
20588        "
20589        .unindent(),
20590        cx,
20591    )
20592    .await;
20593
20594    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20595}
20596
20597#[gpui::test]
20598async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20599    let (buffer_id, mut cx) = setup_indent_guides_editor(
20600        &"
20601        def a:
20602        \tb = 3
20603        \tif True:
20604        \t\tc = 4
20605        \t\td = 5
20606        \tprint(b)
20607        "
20608        .unindent(),
20609        cx,
20610    )
20611    .await;
20612
20613    assert_indent_guides(
20614        0..6,
20615        vec![
20616            indent_guide(buffer_id, 1, 5, 0),
20617            indent_guide(buffer_id, 3, 4, 1),
20618        ],
20619        None,
20620        &mut cx,
20621    );
20622}
20623
20624#[gpui::test]
20625async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20626    let (buffer_id, mut cx) = setup_indent_guides_editor(
20627        &"
20628    fn main() {
20629        let a = 1;
20630    }"
20631        .unindent(),
20632        cx,
20633    )
20634    .await;
20635
20636    cx.update_editor(|editor, window, cx| {
20637        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20638            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20639        });
20640    });
20641
20642    assert_indent_guides(
20643        0..3,
20644        vec![indent_guide(buffer_id, 1, 1, 0)],
20645        Some(vec![0]),
20646        &mut cx,
20647    );
20648}
20649
20650#[gpui::test]
20651async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20652    let (buffer_id, mut cx) = setup_indent_guides_editor(
20653        &"
20654    fn main() {
20655        if 1 == 2 {
20656            let a = 1;
20657        }
20658    }"
20659        .unindent(),
20660        cx,
20661    )
20662    .await;
20663
20664    cx.update_editor(|editor, window, cx| {
20665        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20666            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20667        });
20668    });
20669
20670    assert_indent_guides(
20671        0..4,
20672        vec![
20673            indent_guide(buffer_id, 1, 3, 0),
20674            indent_guide(buffer_id, 2, 2, 1),
20675        ],
20676        Some(vec![1]),
20677        &mut cx,
20678    );
20679
20680    cx.update_editor(|editor, window, cx| {
20681        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20682            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20683        });
20684    });
20685
20686    assert_indent_guides(
20687        0..4,
20688        vec![
20689            indent_guide(buffer_id, 1, 3, 0),
20690            indent_guide(buffer_id, 2, 2, 1),
20691        ],
20692        Some(vec![1]),
20693        &mut cx,
20694    );
20695
20696    cx.update_editor(|editor, window, cx| {
20697        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20698            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20699        });
20700    });
20701
20702    assert_indent_guides(
20703        0..4,
20704        vec![
20705            indent_guide(buffer_id, 1, 3, 0),
20706            indent_guide(buffer_id, 2, 2, 1),
20707        ],
20708        Some(vec![0]),
20709        &mut cx,
20710    );
20711}
20712
20713#[gpui::test]
20714async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20715    let (buffer_id, mut cx) = setup_indent_guides_editor(
20716        &"
20717    fn main() {
20718        let a = 1;
20719
20720        let b = 2;
20721    }"
20722        .unindent(),
20723        cx,
20724    )
20725    .await;
20726
20727    cx.update_editor(|editor, window, cx| {
20728        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20729            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20730        });
20731    });
20732
20733    assert_indent_guides(
20734        0..5,
20735        vec![indent_guide(buffer_id, 1, 3, 0)],
20736        Some(vec![0]),
20737        &mut cx,
20738    );
20739}
20740
20741#[gpui::test]
20742async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20743    let (buffer_id, mut cx) = setup_indent_guides_editor(
20744        &"
20745    def m:
20746        a = 1
20747        pass"
20748            .unindent(),
20749        cx,
20750    )
20751    .await;
20752
20753    cx.update_editor(|editor, window, cx| {
20754        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20755            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20756        });
20757    });
20758
20759    assert_indent_guides(
20760        0..3,
20761        vec![indent_guide(buffer_id, 1, 2, 0)],
20762        Some(vec![0]),
20763        &mut cx,
20764    );
20765}
20766
20767#[gpui::test]
20768async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20769    init_test(cx, |_| {});
20770    let mut cx = EditorTestContext::new(cx).await;
20771    let text = indoc! {
20772        "
20773        impl A {
20774            fn b() {
20775                0;
20776                3;
20777                5;
20778                6;
20779                7;
20780            }
20781        }
20782        "
20783    };
20784    let base_text = indoc! {
20785        "
20786        impl A {
20787            fn b() {
20788                0;
20789                1;
20790                2;
20791                3;
20792                4;
20793            }
20794            fn c() {
20795                5;
20796                6;
20797                7;
20798            }
20799        }
20800        "
20801    };
20802
20803    cx.update_editor(|editor, window, cx| {
20804        editor.set_text(text, window, cx);
20805
20806        editor.buffer().update(cx, |multibuffer, cx| {
20807            let buffer = multibuffer.as_singleton().unwrap();
20808            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20809
20810            multibuffer.set_all_diff_hunks_expanded(cx);
20811            multibuffer.add_diff(diff, cx);
20812
20813            buffer.read(cx).remote_id()
20814        })
20815    });
20816    cx.run_until_parked();
20817
20818    cx.assert_state_with_diff(
20819        indoc! { "
20820          impl A {
20821              fn b() {
20822                  0;
20823        -         1;
20824        -         2;
20825                  3;
20826        -         4;
20827        -     }
20828        -     fn c() {
20829                  5;
20830                  6;
20831                  7;
20832              }
20833          }
20834          ˇ"
20835        }
20836        .to_string(),
20837    );
20838
20839    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20840        editor
20841            .snapshot(window, cx)
20842            .buffer_snapshot()
20843            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20844            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20845            .collect::<Vec<_>>()
20846    });
20847    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20848    assert_eq!(
20849        actual_guides,
20850        vec![
20851            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20852            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20853            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20854        ]
20855    );
20856}
20857
20858#[gpui::test]
20859async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20860    init_test(cx, |_| {});
20861    let mut cx = EditorTestContext::new(cx).await;
20862
20863    let diff_base = r#"
20864        a
20865        b
20866        c
20867        "#
20868    .unindent();
20869
20870    cx.set_state(
20871        &r#"
20872        ˇA
20873        b
20874        C
20875        "#
20876        .unindent(),
20877    );
20878    cx.set_head_text(&diff_base);
20879    cx.update_editor(|editor, window, cx| {
20880        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20881    });
20882    executor.run_until_parked();
20883
20884    let both_hunks_expanded = r#"
20885        - a
20886        + ˇA
20887          b
20888        - c
20889        + C
20890        "#
20891    .unindent();
20892
20893    cx.assert_state_with_diff(both_hunks_expanded.clone());
20894
20895    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20896        let snapshot = editor.snapshot(window, cx);
20897        let hunks = editor
20898            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
20899            .collect::<Vec<_>>();
20900        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20901        let buffer_id = hunks[0].buffer_id;
20902        hunks
20903            .into_iter()
20904            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20905            .collect::<Vec<_>>()
20906    });
20907    assert_eq!(hunk_ranges.len(), 2);
20908
20909    cx.update_editor(|editor, _, cx| {
20910        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20911    });
20912    executor.run_until_parked();
20913
20914    let second_hunk_expanded = r#"
20915          ˇA
20916          b
20917        - c
20918        + C
20919        "#
20920    .unindent();
20921
20922    cx.assert_state_with_diff(second_hunk_expanded);
20923
20924    cx.update_editor(|editor, _, cx| {
20925        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20926    });
20927    executor.run_until_parked();
20928
20929    cx.assert_state_with_diff(both_hunks_expanded.clone());
20930
20931    cx.update_editor(|editor, _, cx| {
20932        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20933    });
20934    executor.run_until_parked();
20935
20936    let first_hunk_expanded = r#"
20937        - a
20938        + ˇA
20939          b
20940          C
20941        "#
20942    .unindent();
20943
20944    cx.assert_state_with_diff(first_hunk_expanded);
20945
20946    cx.update_editor(|editor, _, cx| {
20947        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20948    });
20949    executor.run_until_parked();
20950
20951    cx.assert_state_with_diff(both_hunks_expanded);
20952
20953    cx.set_state(
20954        &r#"
20955        ˇA
20956        b
20957        "#
20958        .unindent(),
20959    );
20960    cx.run_until_parked();
20961
20962    // TODO this cursor position seems bad
20963    cx.assert_state_with_diff(
20964        r#"
20965        - ˇa
20966        + A
20967          b
20968        "#
20969        .unindent(),
20970    );
20971
20972    cx.update_editor(|editor, window, cx| {
20973        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20974    });
20975
20976    cx.assert_state_with_diff(
20977        r#"
20978            - ˇa
20979            + A
20980              b
20981            - c
20982            "#
20983        .unindent(),
20984    );
20985
20986    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20987        let snapshot = editor.snapshot(window, cx);
20988        let hunks = editor
20989            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
20990            .collect::<Vec<_>>();
20991        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20992        let buffer_id = hunks[0].buffer_id;
20993        hunks
20994            .into_iter()
20995            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20996            .collect::<Vec<_>>()
20997    });
20998    assert_eq!(hunk_ranges.len(), 2);
20999
21000    cx.update_editor(|editor, _, cx| {
21001        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21002    });
21003    executor.run_until_parked();
21004
21005    cx.assert_state_with_diff(
21006        r#"
21007        - ˇa
21008        + A
21009          b
21010        "#
21011        .unindent(),
21012    );
21013}
21014
21015#[gpui::test]
21016async fn test_toggle_deletion_hunk_at_start_of_file(
21017    executor: BackgroundExecutor,
21018    cx: &mut TestAppContext,
21019) {
21020    init_test(cx, |_| {});
21021    let mut cx = EditorTestContext::new(cx).await;
21022
21023    let diff_base = r#"
21024        a
21025        b
21026        c
21027        "#
21028    .unindent();
21029
21030    cx.set_state(
21031        &r#"
21032        ˇb
21033        c
21034        "#
21035        .unindent(),
21036    );
21037    cx.set_head_text(&diff_base);
21038    cx.update_editor(|editor, window, cx| {
21039        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21040    });
21041    executor.run_until_parked();
21042
21043    let hunk_expanded = r#"
21044        - a
21045          ˇb
21046          c
21047        "#
21048    .unindent();
21049
21050    cx.assert_state_with_diff(hunk_expanded.clone());
21051
21052    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21053        let snapshot = editor.snapshot(window, cx);
21054        let hunks = editor
21055            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21056            .collect::<Vec<_>>();
21057        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21058        let buffer_id = hunks[0].buffer_id;
21059        hunks
21060            .into_iter()
21061            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21062            .collect::<Vec<_>>()
21063    });
21064    assert_eq!(hunk_ranges.len(), 1);
21065
21066    cx.update_editor(|editor, _, cx| {
21067        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21068    });
21069    executor.run_until_parked();
21070
21071    let hunk_collapsed = r#"
21072          ˇb
21073          c
21074        "#
21075    .unindent();
21076
21077    cx.assert_state_with_diff(hunk_collapsed);
21078
21079    cx.update_editor(|editor, _, cx| {
21080        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21081    });
21082    executor.run_until_parked();
21083
21084    cx.assert_state_with_diff(hunk_expanded);
21085}
21086
21087#[gpui::test]
21088async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21089    init_test(cx, |_| {});
21090
21091    let fs = FakeFs::new(cx.executor());
21092    fs.insert_tree(
21093        path!("/test"),
21094        json!({
21095            ".git": {},
21096            "file-1": "ONE\n",
21097            "file-2": "TWO\n",
21098            "file-3": "THREE\n",
21099        }),
21100    )
21101    .await;
21102
21103    fs.set_head_for_repo(
21104        path!("/test/.git").as_ref(),
21105        &[
21106            ("file-1", "one\n".into()),
21107            ("file-2", "two\n".into()),
21108            ("file-3", "three\n".into()),
21109        ],
21110        "deadbeef",
21111    );
21112
21113    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21114    let mut buffers = vec![];
21115    for i in 1..=3 {
21116        let buffer = project
21117            .update(cx, |project, cx| {
21118                let path = format!(path!("/test/file-{}"), i);
21119                project.open_local_buffer(path, cx)
21120            })
21121            .await
21122            .unwrap();
21123        buffers.push(buffer);
21124    }
21125
21126    let multibuffer = cx.new(|cx| {
21127        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21128        multibuffer.set_all_diff_hunks_expanded(cx);
21129        for buffer in &buffers {
21130            let snapshot = buffer.read(cx).snapshot();
21131            multibuffer.set_excerpts_for_path(
21132                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21133                buffer.clone(),
21134                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21135                2,
21136                cx,
21137            );
21138        }
21139        multibuffer
21140    });
21141
21142    let editor = cx.add_window(|window, cx| {
21143        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21144    });
21145    cx.run_until_parked();
21146
21147    let snapshot = editor
21148        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21149        .unwrap();
21150    let hunks = snapshot
21151        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21152        .map(|hunk| match hunk {
21153            DisplayDiffHunk::Unfolded {
21154                display_row_range, ..
21155            } => display_row_range,
21156            DisplayDiffHunk::Folded { .. } => unreachable!(),
21157        })
21158        .collect::<Vec<_>>();
21159    assert_eq!(
21160        hunks,
21161        [
21162            DisplayRow(2)..DisplayRow(4),
21163            DisplayRow(7)..DisplayRow(9),
21164            DisplayRow(12)..DisplayRow(14),
21165        ]
21166    );
21167}
21168
21169#[gpui::test]
21170async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21171    init_test(cx, |_| {});
21172
21173    let mut cx = EditorTestContext::new(cx).await;
21174    cx.set_head_text(indoc! { "
21175        one
21176        two
21177        three
21178        four
21179        five
21180        "
21181    });
21182    cx.set_index_text(indoc! { "
21183        one
21184        two
21185        three
21186        four
21187        five
21188        "
21189    });
21190    cx.set_state(indoc! {"
21191        one
21192        TWO
21193        ˇTHREE
21194        FOUR
21195        five
21196    "});
21197    cx.run_until_parked();
21198    cx.update_editor(|editor, window, cx| {
21199        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21200    });
21201    cx.run_until_parked();
21202    cx.assert_index_text(Some(indoc! {"
21203        one
21204        TWO
21205        THREE
21206        FOUR
21207        five
21208    "}));
21209    cx.set_state(indoc! { "
21210        one
21211        TWO
21212        ˇTHREE-HUNDRED
21213        FOUR
21214        five
21215    "});
21216    cx.run_until_parked();
21217    cx.update_editor(|editor, window, cx| {
21218        let snapshot = editor.snapshot(window, cx);
21219        let hunks = editor
21220            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21221            .collect::<Vec<_>>();
21222        assert_eq!(hunks.len(), 1);
21223        assert_eq!(
21224            hunks[0].status(),
21225            DiffHunkStatus {
21226                kind: DiffHunkStatusKind::Modified,
21227                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21228            }
21229        );
21230
21231        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21232    });
21233    cx.run_until_parked();
21234    cx.assert_index_text(Some(indoc! {"
21235        one
21236        TWO
21237        THREE-HUNDRED
21238        FOUR
21239        five
21240    "}));
21241}
21242
21243#[gpui::test]
21244fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21245    init_test(cx, |_| {});
21246
21247    let editor = cx.add_window(|window, cx| {
21248        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21249        build_editor(buffer, window, cx)
21250    });
21251
21252    let render_args = Arc::new(Mutex::new(None));
21253    let snapshot = editor
21254        .update(cx, |editor, window, cx| {
21255            let snapshot = editor.buffer().read(cx).snapshot(cx);
21256            let range =
21257                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21258
21259            struct RenderArgs {
21260                row: MultiBufferRow,
21261                folded: bool,
21262                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21263            }
21264
21265            let crease = Crease::inline(
21266                range,
21267                FoldPlaceholder::test(),
21268                {
21269                    let toggle_callback = render_args.clone();
21270                    move |row, folded, callback, _window, _cx| {
21271                        *toggle_callback.lock() = Some(RenderArgs {
21272                            row,
21273                            folded,
21274                            callback,
21275                        });
21276                        div()
21277                    }
21278                },
21279                |_row, _folded, _window, _cx| div(),
21280            );
21281
21282            editor.insert_creases(Some(crease), cx);
21283            let snapshot = editor.snapshot(window, cx);
21284            let _div =
21285                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21286            snapshot
21287        })
21288        .unwrap();
21289
21290    let render_args = render_args.lock().take().unwrap();
21291    assert_eq!(render_args.row, MultiBufferRow(1));
21292    assert!(!render_args.folded);
21293    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21294
21295    cx.update_window(*editor, |_, window, cx| {
21296        (render_args.callback)(true, window, cx)
21297    })
21298    .unwrap();
21299    let snapshot = editor
21300        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21301        .unwrap();
21302    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21303
21304    cx.update_window(*editor, |_, window, cx| {
21305        (render_args.callback)(false, window, cx)
21306    })
21307    .unwrap();
21308    let snapshot = editor
21309        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21310        .unwrap();
21311    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21312}
21313
21314#[gpui::test]
21315async fn test_input_text(cx: &mut TestAppContext) {
21316    init_test(cx, |_| {});
21317    let mut cx = EditorTestContext::new(cx).await;
21318
21319    cx.set_state(
21320        &r#"ˇone
21321        two
21322
21323        three
21324        fourˇ
21325        five
21326
21327        siˇx"#
21328            .unindent(),
21329    );
21330
21331    cx.dispatch_action(HandleInput(String::new()));
21332    cx.assert_editor_state(
21333        &r#"ˇone
21334        two
21335
21336        three
21337        fourˇ
21338        five
21339
21340        siˇx"#
21341            .unindent(),
21342    );
21343
21344    cx.dispatch_action(HandleInput("AAAA".to_string()));
21345    cx.assert_editor_state(
21346        &r#"AAAAˇone
21347        two
21348
21349        three
21350        fourAAAAˇ
21351        five
21352
21353        siAAAAˇx"#
21354            .unindent(),
21355    );
21356}
21357
21358#[gpui::test]
21359async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21360    init_test(cx, |_| {});
21361
21362    let mut cx = EditorTestContext::new(cx).await;
21363    cx.set_state(
21364        r#"let foo = 1;
21365let foo = 2;
21366let foo = 3;
21367let fooˇ = 4;
21368let foo = 5;
21369let foo = 6;
21370let foo = 7;
21371let foo = 8;
21372let foo = 9;
21373let foo = 10;
21374let foo = 11;
21375let foo = 12;
21376let foo = 13;
21377let foo = 14;
21378let foo = 15;"#,
21379    );
21380
21381    cx.update_editor(|e, window, cx| {
21382        assert_eq!(
21383            e.next_scroll_position,
21384            NextScrollCursorCenterTopBottom::Center,
21385            "Default next scroll direction is center",
21386        );
21387
21388        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21389        assert_eq!(
21390            e.next_scroll_position,
21391            NextScrollCursorCenterTopBottom::Top,
21392            "After center, next scroll direction should be top",
21393        );
21394
21395        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21396        assert_eq!(
21397            e.next_scroll_position,
21398            NextScrollCursorCenterTopBottom::Bottom,
21399            "After top, next scroll direction should be bottom",
21400        );
21401
21402        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21403        assert_eq!(
21404            e.next_scroll_position,
21405            NextScrollCursorCenterTopBottom::Center,
21406            "After bottom, scrolling should start over",
21407        );
21408
21409        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21410        assert_eq!(
21411            e.next_scroll_position,
21412            NextScrollCursorCenterTopBottom::Top,
21413            "Scrolling continues if retriggered fast enough"
21414        );
21415    });
21416
21417    cx.executor()
21418        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21419    cx.executor().run_until_parked();
21420    cx.update_editor(|e, _, _| {
21421        assert_eq!(
21422            e.next_scroll_position,
21423            NextScrollCursorCenterTopBottom::Center,
21424            "If scrolling is not triggered fast enough, it should reset"
21425        );
21426    });
21427}
21428
21429#[gpui::test]
21430async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21431    init_test(cx, |_| {});
21432    let mut cx = EditorLspTestContext::new_rust(
21433        lsp::ServerCapabilities {
21434            definition_provider: Some(lsp::OneOf::Left(true)),
21435            references_provider: Some(lsp::OneOf::Left(true)),
21436            ..lsp::ServerCapabilities::default()
21437        },
21438        cx,
21439    )
21440    .await;
21441
21442    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21443        let go_to_definition = cx
21444            .lsp
21445            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21446                move |params, _| async move {
21447                    if empty_go_to_definition {
21448                        Ok(None)
21449                    } else {
21450                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21451                            uri: params.text_document_position_params.text_document.uri,
21452                            range: lsp::Range::new(
21453                                lsp::Position::new(4, 3),
21454                                lsp::Position::new(4, 6),
21455                            ),
21456                        })))
21457                    }
21458                },
21459            );
21460        let references = cx
21461            .lsp
21462            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21463                Ok(Some(vec![lsp::Location {
21464                    uri: params.text_document_position.text_document.uri,
21465                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21466                }]))
21467            });
21468        (go_to_definition, references)
21469    };
21470
21471    cx.set_state(
21472        &r#"fn one() {
21473            let mut a = ˇtwo();
21474        }
21475
21476        fn two() {}"#
21477            .unindent(),
21478    );
21479    set_up_lsp_handlers(false, &mut cx);
21480    let navigated = cx
21481        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21482        .await
21483        .expect("Failed to navigate to definition");
21484    assert_eq!(
21485        navigated,
21486        Navigated::Yes,
21487        "Should have navigated to definition from the GetDefinition response"
21488    );
21489    cx.assert_editor_state(
21490        &r#"fn one() {
21491            let mut a = two();
21492        }
21493
21494        fn «twoˇ»() {}"#
21495            .unindent(),
21496    );
21497
21498    let editors = cx.update_workspace(|workspace, _, cx| {
21499        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21500    });
21501    cx.update_editor(|_, _, test_editor_cx| {
21502        assert_eq!(
21503            editors.len(),
21504            1,
21505            "Initially, only one, test, editor should be open in the workspace"
21506        );
21507        assert_eq!(
21508            test_editor_cx.entity(),
21509            editors.last().expect("Asserted len is 1").clone()
21510        );
21511    });
21512
21513    set_up_lsp_handlers(true, &mut cx);
21514    let navigated = cx
21515        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21516        .await
21517        .expect("Failed to navigate to lookup references");
21518    assert_eq!(
21519        navigated,
21520        Navigated::Yes,
21521        "Should have navigated to references as a fallback after empty GoToDefinition response"
21522    );
21523    // We should not change the selections in the existing file,
21524    // if opening another milti buffer with the references
21525    cx.assert_editor_state(
21526        &r#"fn one() {
21527            let mut a = two();
21528        }
21529
21530        fn «twoˇ»() {}"#
21531            .unindent(),
21532    );
21533    let editors = cx.update_workspace(|workspace, _, cx| {
21534        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21535    });
21536    cx.update_editor(|_, _, test_editor_cx| {
21537        assert_eq!(
21538            editors.len(),
21539            2,
21540            "After falling back to references search, we open a new editor with the results"
21541        );
21542        let references_fallback_text = editors
21543            .into_iter()
21544            .find(|new_editor| *new_editor != test_editor_cx.entity())
21545            .expect("Should have one non-test editor now")
21546            .read(test_editor_cx)
21547            .text(test_editor_cx);
21548        assert_eq!(
21549            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21550            "Should use the range from the references response and not the GoToDefinition one"
21551        );
21552    });
21553}
21554
21555#[gpui::test]
21556async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21557    init_test(cx, |_| {});
21558    cx.update(|cx| {
21559        let mut editor_settings = EditorSettings::get_global(cx).clone();
21560        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21561        EditorSettings::override_global(editor_settings, cx);
21562    });
21563    let mut cx = EditorLspTestContext::new_rust(
21564        lsp::ServerCapabilities {
21565            definition_provider: Some(lsp::OneOf::Left(true)),
21566            references_provider: Some(lsp::OneOf::Left(true)),
21567            ..lsp::ServerCapabilities::default()
21568        },
21569        cx,
21570    )
21571    .await;
21572    let original_state = r#"fn one() {
21573        let mut a = ˇtwo();
21574    }
21575
21576    fn two() {}"#
21577        .unindent();
21578    cx.set_state(&original_state);
21579
21580    let mut go_to_definition = cx
21581        .lsp
21582        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21583            move |_, _| async move { Ok(None) },
21584        );
21585    let _references = cx
21586        .lsp
21587        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21588            panic!("Should not call for references with no go to definition fallback")
21589        });
21590
21591    let navigated = cx
21592        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21593        .await
21594        .expect("Failed to navigate to lookup references");
21595    go_to_definition
21596        .next()
21597        .await
21598        .expect("Should have called the go_to_definition handler");
21599
21600    assert_eq!(
21601        navigated,
21602        Navigated::No,
21603        "Should have navigated to references as a fallback after empty GoToDefinition response"
21604    );
21605    cx.assert_editor_state(&original_state);
21606    let editors = cx.update_workspace(|workspace, _, cx| {
21607        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21608    });
21609    cx.update_editor(|_, _, _| {
21610        assert_eq!(
21611            editors.len(),
21612            1,
21613            "After unsuccessful fallback, no other editor should have been opened"
21614        );
21615    });
21616}
21617
21618#[gpui::test]
21619async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21620    init_test(cx, |_| {});
21621    let mut cx = EditorLspTestContext::new_rust(
21622        lsp::ServerCapabilities {
21623            references_provider: Some(lsp::OneOf::Left(true)),
21624            ..lsp::ServerCapabilities::default()
21625        },
21626        cx,
21627    )
21628    .await;
21629
21630    cx.set_state(
21631        &r#"
21632        fn one() {
21633            let mut a = two();
21634        }
21635
21636        fn ˇtwo() {}"#
21637            .unindent(),
21638    );
21639    cx.lsp
21640        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21641            Ok(Some(vec![
21642                lsp::Location {
21643                    uri: params.text_document_position.text_document.uri.clone(),
21644                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21645                },
21646                lsp::Location {
21647                    uri: params.text_document_position.text_document.uri,
21648                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21649                },
21650            ]))
21651        });
21652    let navigated = cx
21653        .update_editor(|editor, window, cx| {
21654            editor.find_all_references(&FindAllReferences, window, cx)
21655        })
21656        .unwrap()
21657        .await
21658        .expect("Failed to navigate to references");
21659    assert_eq!(
21660        navigated,
21661        Navigated::Yes,
21662        "Should have navigated to references from the FindAllReferences response"
21663    );
21664    cx.assert_editor_state(
21665        &r#"fn one() {
21666            let mut a = two();
21667        }
21668
21669        fn ˇtwo() {}"#
21670            .unindent(),
21671    );
21672
21673    let editors = cx.update_workspace(|workspace, _, cx| {
21674        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21675    });
21676    cx.update_editor(|_, _, _| {
21677        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21678    });
21679
21680    cx.set_state(
21681        &r#"fn one() {
21682            let mut a = ˇtwo();
21683        }
21684
21685        fn two() {}"#
21686            .unindent(),
21687    );
21688    let navigated = cx
21689        .update_editor(|editor, window, cx| {
21690            editor.find_all_references(&FindAllReferences, window, cx)
21691        })
21692        .unwrap()
21693        .await
21694        .expect("Failed to navigate to references");
21695    assert_eq!(
21696        navigated,
21697        Navigated::Yes,
21698        "Should have navigated to references from the FindAllReferences response"
21699    );
21700    cx.assert_editor_state(
21701        &r#"fn one() {
21702            let mut a = ˇtwo();
21703        }
21704
21705        fn two() {}"#
21706            .unindent(),
21707    );
21708    let editors = cx.update_workspace(|workspace, _, cx| {
21709        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21710    });
21711    cx.update_editor(|_, _, _| {
21712        assert_eq!(
21713            editors.len(),
21714            2,
21715            "should have re-used the previous multibuffer"
21716        );
21717    });
21718
21719    cx.set_state(
21720        &r#"fn one() {
21721            let mut a = ˇtwo();
21722        }
21723        fn three() {}
21724        fn two() {}"#
21725            .unindent(),
21726    );
21727    cx.lsp
21728        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21729            Ok(Some(vec![
21730                lsp::Location {
21731                    uri: params.text_document_position.text_document.uri.clone(),
21732                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21733                },
21734                lsp::Location {
21735                    uri: params.text_document_position.text_document.uri,
21736                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21737                },
21738            ]))
21739        });
21740    let navigated = cx
21741        .update_editor(|editor, window, cx| {
21742            editor.find_all_references(&FindAllReferences, window, cx)
21743        })
21744        .unwrap()
21745        .await
21746        .expect("Failed to navigate to references");
21747    assert_eq!(
21748        navigated,
21749        Navigated::Yes,
21750        "Should have navigated to references from the FindAllReferences response"
21751    );
21752    cx.assert_editor_state(
21753        &r#"fn one() {
21754                let mut a = ˇtwo();
21755            }
21756            fn three() {}
21757            fn two() {}"#
21758            .unindent(),
21759    );
21760    let editors = cx.update_workspace(|workspace, _, cx| {
21761        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21762    });
21763    cx.update_editor(|_, _, _| {
21764        assert_eq!(
21765            editors.len(),
21766            3,
21767            "should have used a new multibuffer as offsets changed"
21768        );
21769    });
21770}
21771#[gpui::test]
21772async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21773    init_test(cx, |_| {});
21774
21775    let language = Arc::new(Language::new(
21776        LanguageConfig::default(),
21777        Some(tree_sitter_rust::LANGUAGE.into()),
21778    ));
21779
21780    let text = r#"
21781        #[cfg(test)]
21782        mod tests() {
21783            #[test]
21784            fn runnable_1() {
21785                let a = 1;
21786            }
21787
21788            #[test]
21789            fn runnable_2() {
21790                let a = 1;
21791                let b = 2;
21792            }
21793        }
21794    "#
21795    .unindent();
21796
21797    let fs = FakeFs::new(cx.executor());
21798    fs.insert_file("/file.rs", Default::default()).await;
21799
21800    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21801    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21802    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21803    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21804    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21805
21806    let editor = cx.new_window_entity(|window, cx| {
21807        Editor::new(
21808            EditorMode::full(),
21809            multi_buffer,
21810            Some(project.clone()),
21811            window,
21812            cx,
21813        )
21814    });
21815
21816    editor.update_in(cx, |editor, window, cx| {
21817        let snapshot = editor.buffer().read(cx).snapshot(cx);
21818        editor.tasks.insert(
21819            (buffer.read(cx).remote_id(), 3),
21820            RunnableTasks {
21821                templates: vec![],
21822                offset: snapshot.anchor_before(43),
21823                column: 0,
21824                extra_variables: HashMap::default(),
21825                context_range: BufferOffset(43)..BufferOffset(85),
21826            },
21827        );
21828        editor.tasks.insert(
21829            (buffer.read(cx).remote_id(), 8),
21830            RunnableTasks {
21831                templates: vec![],
21832                offset: snapshot.anchor_before(86),
21833                column: 0,
21834                extra_variables: HashMap::default(),
21835                context_range: BufferOffset(86)..BufferOffset(191),
21836            },
21837        );
21838
21839        // Test finding task when cursor is inside function body
21840        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21841            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21842        });
21843        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21844        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21845
21846        // Test finding task when cursor is on function name
21847        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21848            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21849        });
21850        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21851        assert_eq!(row, 8, "Should find task when cursor is on function name");
21852    });
21853}
21854
21855#[gpui::test]
21856async fn test_folding_buffers(cx: &mut TestAppContext) {
21857    init_test(cx, |_| {});
21858
21859    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21860    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21861    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21862
21863    let fs = FakeFs::new(cx.executor());
21864    fs.insert_tree(
21865        path!("/a"),
21866        json!({
21867            "first.rs": sample_text_1,
21868            "second.rs": sample_text_2,
21869            "third.rs": sample_text_3,
21870        }),
21871    )
21872    .await;
21873    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21874    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21875    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21876    let worktree = project.update(cx, |project, cx| {
21877        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21878        assert_eq!(worktrees.len(), 1);
21879        worktrees.pop().unwrap()
21880    });
21881    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21882
21883    let buffer_1 = project
21884        .update(cx, |project, cx| {
21885            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
21886        })
21887        .await
21888        .unwrap();
21889    let buffer_2 = project
21890        .update(cx, |project, cx| {
21891            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
21892        })
21893        .await
21894        .unwrap();
21895    let buffer_3 = project
21896        .update(cx, |project, cx| {
21897            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
21898        })
21899        .await
21900        .unwrap();
21901
21902    let multi_buffer = cx.new(|cx| {
21903        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21904        multi_buffer.push_excerpts(
21905            buffer_1.clone(),
21906            [
21907                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21908                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21909                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21910            ],
21911            cx,
21912        );
21913        multi_buffer.push_excerpts(
21914            buffer_2.clone(),
21915            [
21916                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21917                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21918                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21919            ],
21920            cx,
21921        );
21922        multi_buffer.push_excerpts(
21923            buffer_3.clone(),
21924            [
21925                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21926                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21927                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21928            ],
21929            cx,
21930        );
21931        multi_buffer
21932    });
21933    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21934        Editor::new(
21935            EditorMode::full(),
21936            multi_buffer.clone(),
21937            Some(project.clone()),
21938            window,
21939            cx,
21940        )
21941    });
21942
21943    assert_eq!(
21944        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21945        "\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",
21946    );
21947
21948    multi_buffer_editor.update(cx, |editor, cx| {
21949        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21950    });
21951    assert_eq!(
21952        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21953        "\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",
21954        "After folding the first buffer, its text should not be displayed"
21955    );
21956
21957    multi_buffer_editor.update(cx, |editor, cx| {
21958        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21959    });
21960    assert_eq!(
21961        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21962        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21963        "After folding the second buffer, its text should not be displayed"
21964    );
21965
21966    multi_buffer_editor.update(cx, |editor, cx| {
21967        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21968    });
21969    assert_eq!(
21970        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21971        "\n\n\n\n\n",
21972        "After folding the third buffer, its text should not be displayed"
21973    );
21974
21975    // Emulate selection inside the fold logic, that should work
21976    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21977        editor
21978            .snapshot(window, cx)
21979            .next_line_boundary(Point::new(0, 4));
21980    });
21981
21982    multi_buffer_editor.update(cx, |editor, cx| {
21983        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21984    });
21985    assert_eq!(
21986        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21987        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21988        "After unfolding the second buffer, its text should be displayed"
21989    );
21990
21991    // Typing inside of buffer 1 causes that buffer to be unfolded.
21992    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21993        assert_eq!(
21994            multi_buffer
21995                .read(cx)
21996                .snapshot(cx)
21997                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21998                .collect::<String>(),
21999            "bbbb"
22000        );
22001        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22002            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22003        });
22004        editor.handle_input("B", window, cx);
22005    });
22006
22007    assert_eq!(
22008        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22009        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22010        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22011    );
22012
22013    multi_buffer_editor.update(cx, |editor, cx| {
22014        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22015    });
22016    assert_eq!(
22017        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22018        "\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",
22019        "After unfolding the all buffers, all original text should be displayed"
22020    );
22021}
22022
22023#[gpui::test]
22024async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22025    init_test(cx, |_| {});
22026
22027    let sample_text_1 = "1111\n2222\n3333".to_string();
22028    let sample_text_2 = "4444\n5555\n6666".to_string();
22029    let sample_text_3 = "7777\n8888\n9999".to_string();
22030
22031    let fs = FakeFs::new(cx.executor());
22032    fs.insert_tree(
22033        path!("/a"),
22034        json!({
22035            "first.rs": sample_text_1,
22036            "second.rs": sample_text_2,
22037            "third.rs": sample_text_3,
22038        }),
22039    )
22040    .await;
22041    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22042    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22043    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22044    let worktree = project.update(cx, |project, cx| {
22045        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22046        assert_eq!(worktrees.len(), 1);
22047        worktrees.pop().unwrap()
22048    });
22049    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22050
22051    let buffer_1 = project
22052        .update(cx, |project, cx| {
22053            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22054        })
22055        .await
22056        .unwrap();
22057    let buffer_2 = project
22058        .update(cx, |project, cx| {
22059            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22060        })
22061        .await
22062        .unwrap();
22063    let buffer_3 = project
22064        .update(cx, |project, cx| {
22065            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22066        })
22067        .await
22068        .unwrap();
22069
22070    let multi_buffer = cx.new(|cx| {
22071        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22072        multi_buffer.push_excerpts(
22073            buffer_1.clone(),
22074            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22075            cx,
22076        );
22077        multi_buffer.push_excerpts(
22078            buffer_2.clone(),
22079            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22080            cx,
22081        );
22082        multi_buffer.push_excerpts(
22083            buffer_3.clone(),
22084            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22085            cx,
22086        );
22087        multi_buffer
22088    });
22089
22090    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22091        Editor::new(
22092            EditorMode::full(),
22093            multi_buffer,
22094            Some(project.clone()),
22095            window,
22096            cx,
22097        )
22098    });
22099
22100    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22101    assert_eq!(
22102        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22103        full_text,
22104    );
22105
22106    multi_buffer_editor.update(cx, |editor, cx| {
22107        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22108    });
22109    assert_eq!(
22110        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22111        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22112        "After folding the first buffer, its text should not be displayed"
22113    );
22114
22115    multi_buffer_editor.update(cx, |editor, cx| {
22116        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22117    });
22118
22119    assert_eq!(
22120        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22121        "\n\n\n\n\n\n7777\n8888\n9999",
22122        "After folding the second buffer, its text should not be displayed"
22123    );
22124
22125    multi_buffer_editor.update(cx, |editor, cx| {
22126        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22127    });
22128    assert_eq!(
22129        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22130        "\n\n\n\n\n",
22131        "After folding the third buffer, its text should not be displayed"
22132    );
22133
22134    multi_buffer_editor.update(cx, |editor, cx| {
22135        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22136    });
22137    assert_eq!(
22138        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22139        "\n\n\n\n4444\n5555\n6666\n\n",
22140        "After unfolding the second buffer, its text should be displayed"
22141    );
22142
22143    multi_buffer_editor.update(cx, |editor, cx| {
22144        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22145    });
22146    assert_eq!(
22147        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22148        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22149        "After unfolding the first buffer, its text should be displayed"
22150    );
22151
22152    multi_buffer_editor.update(cx, |editor, cx| {
22153        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22154    });
22155    assert_eq!(
22156        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22157        full_text,
22158        "After unfolding all buffers, all original text should be displayed"
22159    );
22160}
22161
22162#[gpui::test]
22163async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22164    init_test(cx, |_| {});
22165
22166    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22167
22168    let fs = FakeFs::new(cx.executor());
22169    fs.insert_tree(
22170        path!("/a"),
22171        json!({
22172            "main.rs": sample_text,
22173        }),
22174    )
22175    .await;
22176    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22177    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22178    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22179    let worktree = project.update(cx, |project, cx| {
22180        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22181        assert_eq!(worktrees.len(), 1);
22182        worktrees.pop().unwrap()
22183    });
22184    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22185
22186    let buffer_1 = project
22187        .update(cx, |project, cx| {
22188            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22189        })
22190        .await
22191        .unwrap();
22192
22193    let multi_buffer = cx.new(|cx| {
22194        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22195        multi_buffer.push_excerpts(
22196            buffer_1.clone(),
22197            [ExcerptRange::new(
22198                Point::new(0, 0)
22199                    ..Point::new(
22200                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22201                        0,
22202                    ),
22203            )],
22204            cx,
22205        );
22206        multi_buffer
22207    });
22208    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22209        Editor::new(
22210            EditorMode::full(),
22211            multi_buffer,
22212            Some(project.clone()),
22213            window,
22214            cx,
22215        )
22216    });
22217
22218    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22219    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22220        enum TestHighlight {}
22221        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22222        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22223        editor.highlight_text::<TestHighlight>(
22224            vec![highlight_range.clone()],
22225            HighlightStyle::color(Hsla::green()),
22226            cx,
22227        );
22228        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22229            s.select_ranges(Some(highlight_range))
22230        });
22231    });
22232
22233    let full_text = format!("\n\n{sample_text}");
22234    assert_eq!(
22235        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22236        full_text,
22237    );
22238}
22239
22240#[gpui::test]
22241async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22242    init_test(cx, |_| {});
22243    cx.update(|cx| {
22244        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22245            "keymaps/default-linux.json",
22246            cx,
22247        )
22248        .unwrap();
22249        cx.bind_keys(default_key_bindings);
22250    });
22251
22252    let (editor, cx) = cx.add_window_view(|window, cx| {
22253        let multi_buffer = MultiBuffer::build_multi(
22254            [
22255                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22256                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22257                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22258                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22259            ],
22260            cx,
22261        );
22262        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22263
22264        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22265        // fold all but the second buffer, so that we test navigating between two
22266        // adjacent folded buffers, as well as folded buffers at the start and
22267        // end the multibuffer
22268        editor.fold_buffer(buffer_ids[0], cx);
22269        editor.fold_buffer(buffer_ids[2], cx);
22270        editor.fold_buffer(buffer_ids[3], cx);
22271
22272        editor
22273    });
22274    cx.simulate_resize(size(px(1000.), px(1000.)));
22275
22276    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22277    cx.assert_excerpts_with_selections(indoc! {"
22278        [EXCERPT]
22279        ˇ[FOLDED]
22280        [EXCERPT]
22281        a1
22282        b1
22283        [EXCERPT]
22284        [FOLDED]
22285        [EXCERPT]
22286        [FOLDED]
22287        "
22288    });
22289    cx.simulate_keystroke("down");
22290    cx.assert_excerpts_with_selections(indoc! {"
22291        [EXCERPT]
22292        [FOLDED]
22293        [EXCERPT]
22294        ˇa1
22295        b1
22296        [EXCERPT]
22297        [FOLDED]
22298        [EXCERPT]
22299        [FOLDED]
22300        "
22301    });
22302    cx.simulate_keystroke("down");
22303    cx.assert_excerpts_with_selections(indoc! {"
22304        [EXCERPT]
22305        [FOLDED]
22306        [EXCERPT]
22307        a1
22308        ˇb1
22309        [EXCERPT]
22310        [FOLDED]
22311        [EXCERPT]
22312        [FOLDED]
22313        "
22314    });
22315    cx.simulate_keystroke("down");
22316    cx.assert_excerpts_with_selections(indoc! {"
22317        [EXCERPT]
22318        [FOLDED]
22319        [EXCERPT]
22320        a1
22321        b1
22322        ˇ[EXCERPT]
22323        [FOLDED]
22324        [EXCERPT]
22325        [FOLDED]
22326        "
22327    });
22328    cx.simulate_keystroke("down");
22329    cx.assert_excerpts_with_selections(indoc! {"
22330        [EXCERPT]
22331        [FOLDED]
22332        [EXCERPT]
22333        a1
22334        b1
22335        [EXCERPT]
22336        ˇ[FOLDED]
22337        [EXCERPT]
22338        [FOLDED]
22339        "
22340    });
22341    for _ in 0..5 {
22342        cx.simulate_keystroke("down");
22343        cx.assert_excerpts_with_selections(indoc! {"
22344            [EXCERPT]
22345            [FOLDED]
22346            [EXCERPT]
22347            a1
22348            b1
22349            [EXCERPT]
22350            [FOLDED]
22351            [EXCERPT]
22352            ˇ[FOLDED]
22353            "
22354        });
22355    }
22356
22357    cx.simulate_keystroke("up");
22358    cx.assert_excerpts_with_selections(indoc! {"
22359        [EXCERPT]
22360        [FOLDED]
22361        [EXCERPT]
22362        a1
22363        b1
22364        [EXCERPT]
22365        ˇ[FOLDED]
22366        [EXCERPT]
22367        [FOLDED]
22368        "
22369    });
22370    cx.simulate_keystroke("up");
22371    cx.assert_excerpts_with_selections(indoc! {"
22372        [EXCERPT]
22373        [FOLDED]
22374        [EXCERPT]
22375        a1
22376        b1
22377        ˇ[EXCERPT]
22378        [FOLDED]
22379        [EXCERPT]
22380        [FOLDED]
22381        "
22382    });
22383    cx.simulate_keystroke("up");
22384    cx.assert_excerpts_with_selections(indoc! {"
22385        [EXCERPT]
22386        [FOLDED]
22387        [EXCERPT]
22388        a1
22389        ˇb1
22390        [EXCERPT]
22391        [FOLDED]
22392        [EXCERPT]
22393        [FOLDED]
22394        "
22395    });
22396    cx.simulate_keystroke("up");
22397    cx.assert_excerpts_with_selections(indoc! {"
22398        [EXCERPT]
22399        [FOLDED]
22400        [EXCERPT]
22401        ˇa1
22402        b1
22403        [EXCERPT]
22404        [FOLDED]
22405        [EXCERPT]
22406        [FOLDED]
22407        "
22408    });
22409    for _ in 0..5 {
22410        cx.simulate_keystroke("up");
22411        cx.assert_excerpts_with_selections(indoc! {"
22412            [EXCERPT]
22413            ˇ[FOLDED]
22414            [EXCERPT]
22415            a1
22416            b1
22417            [EXCERPT]
22418            [FOLDED]
22419            [EXCERPT]
22420            [FOLDED]
22421            "
22422        });
22423    }
22424}
22425
22426#[gpui::test]
22427async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22428    init_test(cx, |_| {});
22429
22430    // Simple insertion
22431    assert_highlighted_edits(
22432        "Hello, world!",
22433        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22434        true,
22435        cx,
22436        |highlighted_edits, cx| {
22437            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22438            assert_eq!(highlighted_edits.highlights.len(), 1);
22439            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22440            assert_eq!(
22441                highlighted_edits.highlights[0].1.background_color,
22442                Some(cx.theme().status().created_background)
22443            );
22444        },
22445    )
22446    .await;
22447
22448    // Replacement
22449    assert_highlighted_edits(
22450        "This is a test.",
22451        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22452        false,
22453        cx,
22454        |highlighted_edits, cx| {
22455            assert_eq!(highlighted_edits.text, "That is a test.");
22456            assert_eq!(highlighted_edits.highlights.len(), 1);
22457            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22458            assert_eq!(
22459                highlighted_edits.highlights[0].1.background_color,
22460                Some(cx.theme().status().created_background)
22461            );
22462        },
22463    )
22464    .await;
22465
22466    // Multiple edits
22467    assert_highlighted_edits(
22468        "Hello, world!",
22469        vec![
22470            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22471            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22472        ],
22473        false,
22474        cx,
22475        |highlighted_edits, cx| {
22476            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22477            assert_eq!(highlighted_edits.highlights.len(), 2);
22478            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22479            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22480            assert_eq!(
22481                highlighted_edits.highlights[0].1.background_color,
22482                Some(cx.theme().status().created_background)
22483            );
22484            assert_eq!(
22485                highlighted_edits.highlights[1].1.background_color,
22486                Some(cx.theme().status().created_background)
22487            );
22488        },
22489    )
22490    .await;
22491
22492    // Multiple lines with edits
22493    assert_highlighted_edits(
22494        "First line\nSecond line\nThird line\nFourth line",
22495        vec![
22496            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22497            (
22498                Point::new(2, 0)..Point::new(2, 10),
22499                "New third line".to_string(),
22500            ),
22501            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22502        ],
22503        false,
22504        cx,
22505        |highlighted_edits, cx| {
22506            assert_eq!(
22507                highlighted_edits.text,
22508                "Second modified\nNew third line\nFourth updated line"
22509            );
22510            assert_eq!(highlighted_edits.highlights.len(), 3);
22511            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22512            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22513            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22514            for highlight in &highlighted_edits.highlights {
22515                assert_eq!(
22516                    highlight.1.background_color,
22517                    Some(cx.theme().status().created_background)
22518                );
22519            }
22520        },
22521    )
22522    .await;
22523}
22524
22525#[gpui::test]
22526async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22527    init_test(cx, |_| {});
22528
22529    // Deletion
22530    assert_highlighted_edits(
22531        "Hello, world!",
22532        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22533        true,
22534        cx,
22535        |highlighted_edits, cx| {
22536            assert_eq!(highlighted_edits.text, "Hello, world!");
22537            assert_eq!(highlighted_edits.highlights.len(), 1);
22538            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22539            assert_eq!(
22540                highlighted_edits.highlights[0].1.background_color,
22541                Some(cx.theme().status().deleted_background)
22542            );
22543        },
22544    )
22545    .await;
22546
22547    // Insertion
22548    assert_highlighted_edits(
22549        "Hello, world!",
22550        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22551        true,
22552        cx,
22553        |highlighted_edits, cx| {
22554            assert_eq!(highlighted_edits.highlights.len(), 1);
22555            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22556            assert_eq!(
22557                highlighted_edits.highlights[0].1.background_color,
22558                Some(cx.theme().status().created_background)
22559            );
22560        },
22561    )
22562    .await;
22563}
22564
22565async fn assert_highlighted_edits(
22566    text: &str,
22567    edits: Vec<(Range<Point>, String)>,
22568    include_deletions: bool,
22569    cx: &mut TestAppContext,
22570    assertion_fn: impl Fn(HighlightedText, &App),
22571) {
22572    let window = cx.add_window(|window, cx| {
22573        let buffer = MultiBuffer::build_simple(text, cx);
22574        Editor::new(EditorMode::full(), buffer, None, window, cx)
22575    });
22576    let cx = &mut VisualTestContext::from_window(*window, cx);
22577
22578    let (buffer, snapshot) = window
22579        .update(cx, |editor, _window, cx| {
22580            (
22581                editor.buffer().clone(),
22582                editor.buffer().read(cx).snapshot(cx),
22583            )
22584        })
22585        .unwrap();
22586
22587    let edits = edits
22588        .into_iter()
22589        .map(|(range, edit)| {
22590            (
22591                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22592                edit,
22593            )
22594        })
22595        .collect::<Vec<_>>();
22596
22597    let text_anchor_edits = edits
22598        .clone()
22599        .into_iter()
22600        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22601        .collect::<Vec<_>>();
22602
22603    let edit_preview = window
22604        .update(cx, |_, _window, cx| {
22605            buffer
22606                .read(cx)
22607                .as_singleton()
22608                .unwrap()
22609                .read(cx)
22610                .preview_edits(text_anchor_edits.into(), cx)
22611        })
22612        .unwrap()
22613        .await;
22614
22615    cx.update(|_window, cx| {
22616        let highlighted_edits = edit_prediction_edit_text(
22617            snapshot.as_singleton().unwrap().2,
22618            &edits,
22619            &edit_preview,
22620            include_deletions,
22621            cx,
22622        );
22623        assertion_fn(highlighted_edits, cx)
22624    });
22625}
22626
22627#[track_caller]
22628fn assert_breakpoint(
22629    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22630    path: &Arc<Path>,
22631    expected: Vec<(u32, Breakpoint)>,
22632) {
22633    if expected.is_empty() {
22634        assert!(!breakpoints.contains_key(path), "{}", path.display());
22635    } else {
22636        let mut breakpoint = breakpoints
22637            .get(path)
22638            .unwrap()
22639            .iter()
22640            .map(|breakpoint| {
22641                (
22642                    breakpoint.row,
22643                    Breakpoint {
22644                        message: breakpoint.message.clone(),
22645                        state: breakpoint.state,
22646                        condition: breakpoint.condition.clone(),
22647                        hit_condition: breakpoint.hit_condition.clone(),
22648                    },
22649                )
22650            })
22651            .collect::<Vec<_>>();
22652
22653        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22654
22655        assert_eq!(expected, breakpoint);
22656    }
22657}
22658
22659fn add_log_breakpoint_at_cursor(
22660    editor: &mut Editor,
22661    log_message: &str,
22662    window: &mut Window,
22663    cx: &mut Context<Editor>,
22664) {
22665    let (anchor, bp) = editor
22666        .breakpoints_at_cursors(window, cx)
22667        .first()
22668        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22669        .unwrap_or_else(|| {
22670            let cursor_position: Point = editor.selections.newest(cx).head();
22671
22672            let breakpoint_position = editor
22673                .snapshot(window, cx)
22674                .display_snapshot
22675                .buffer_snapshot()
22676                .anchor_before(Point::new(cursor_position.row, 0));
22677
22678            (breakpoint_position, Breakpoint::new_log(log_message))
22679        });
22680
22681    editor.edit_breakpoint_at_anchor(
22682        anchor,
22683        bp,
22684        BreakpointEditAction::EditLogMessage(log_message.into()),
22685        cx,
22686    );
22687}
22688
22689#[gpui::test]
22690async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22691    init_test(cx, |_| {});
22692
22693    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22694    let fs = FakeFs::new(cx.executor());
22695    fs.insert_tree(
22696        path!("/a"),
22697        json!({
22698            "main.rs": sample_text,
22699        }),
22700    )
22701    .await;
22702    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22703    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22704    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22705
22706    let fs = FakeFs::new(cx.executor());
22707    fs.insert_tree(
22708        path!("/a"),
22709        json!({
22710            "main.rs": sample_text,
22711        }),
22712    )
22713    .await;
22714    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22715    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22716    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22717    let worktree_id = workspace
22718        .update(cx, |workspace, _window, cx| {
22719            workspace.project().update(cx, |project, cx| {
22720                project.worktrees(cx).next().unwrap().read(cx).id()
22721            })
22722        })
22723        .unwrap();
22724
22725    let buffer = project
22726        .update(cx, |project, cx| {
22727            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22728        })
22729        .await
22730        .unwrap();
22731
22732    let (editor, cx) = cx.add_window_view(|window, cx| {
22733        Editor::new(
22734            EditorMode::full(),
22735            MultiBuffer::build_from_buffer(buffer, cx),
22736            Some(project.clone()),
22737            window,
22738            cx,
22739        )
22740    });
22741
22742    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22743    let abs_path = project.read_with(cx, |project, cx| {
22744        project
22745            .absolute_path(&project_path, cx)
22746            .map(Arc::from)
22747            .unwrap()
22748    });
22749
22750    // assert we can add breakpoint on the first line
22751    editor.update_in(cx, |editor, window, cx| {
22752        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22753        editor.move_to_end(&MoveToEnd, window, cx);
22754        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22755    });
22756
22757    let breakpoints = editor.update(cx, |editor, cx| {
22758        editor
22759            .breakpoint_store()
22760            .as_ref()
22761            .unwrap()
22762            .read(cx)
22763            .all_source_breakpoints(cx)
22764    });
22765
22766    assert_eq!(1, breakpoints.len());
22767    assert_breakpoint(
22768        &breakpoints,
22769        &abs_path,
22770        vec![
22771            (0, Breakpoint::new_standard()),
22772            (3, Breakpoint::new_standard()),
22773        ],
22774    );
22775
22776    editor.update_in(cx, |editor, window, cx| {
22777        editor.move_to_beginning(&MoveToBeginning, window, cx);
22778        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22779    });
22780
22781    let breakpoints = editor.update(cx, |editor, cx| {
22782        editor
22783            .breakpoint_store()
22784            .as_ref()
22785            .unwrap()
22786            .read(cx)
22787            .all_source_breakpoints(cx)
22788    });
22789
22790    assert_eq!(1, breakpoints.len());
22791    assert_breakpoint(
22792        &breakpoints,
22793        &abs_path,
22794        vec![(3, Breakpoint::new_standard())],
22795    );
22796
22797    editor.update_in(cx, |editor, window, cx| {
22798        editor.move_to_end(&MoveToEnd, window, cx);
22799        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22800    });
22801
22802    let breakpoints = editor.update(cx, |editor, cx| {
22803        editor
22804            .breakpoint_store()
22805            .as_ref()
22806            .unwrap()
22807            .read(cx)
22808            .all_source_breakpoints(cx)
22809    });
22810
22811    assert_eq!(0, breakpoints.len());
22812    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22813}
22814
22815#[gpui::test]
22816async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22817    init_test(cx, |_| {});
22818
22819    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22820
22821    let fs = FakeFs::new(cx.executor());
22822    fs.insert_tree(
22823        path!("/a"),
22824        json!({
22825            "main.rs": sample_text,
22826        }),
22827    )
22828    .await;
22829    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22830    let (workspace, cx) =
22831        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22832
22833    let worktree_id = workspace.update(cx, |workspace, cx| {
22834        workspace.project().update(cx, |project, cx| {
22835            project.worktrees(cx).next().unwrap().read(cx).id()
22836        })
22837    });
22838
22839    let buffer = project
22840        .update(cx, |project, cx| {
22841            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22842        })
22843        .await
22844        .unwrap();
22845
22846    let (editor, cx) = cx.add_window_view(|window, cx| {
22847        Editor::new(
22848            EditorMode::full(),
22849            MultiBuffer::build_from_buffer(buffer, cx),
22850            Some(project.clone()),
22851            window,
22852            cx,
22853        )
22854    });
22855
22856    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22857    let abs_path = project.read_with(cx, |project, cx| {
22858        project
22859            .absolute_path(&project_path, cx)
22860            .map(Arc::from)
22861            .unwrap()
22862    });
22863
22864    editor.update_in(cx, |editor, window, cx| {
22865        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22866    });
22867
22868    let breakpoints = editor.update(cx, |editor, cx| {
22869        editor
22870            .breakpoint_store()
22871            .as_ref()
22872            .unwrap()
22873            .read(cx)
22874            .all_source_breakpoints(cx)
22875    });
22876
22877    assert_breakpoint(
22878        &breakpoints,
22879        &abs_path,
22880        vec![(0, Breakpoint::new_log("hello world"))],
22881    );
22882
22883    // Removing a log message from a log breakpoint should remove it
22884    editor.update_in(cx, |editor, window, cx| {
22885        add_log_breakpoint_at_cursor(editor, "", window, cx);
22886    });
22887
22888    let breakpoints = editor.update(cx, |editor, cx| {
22889        editor
22890            .breakpoint_store()
22891            .as_ref()
22892            .unwrap()
22893            .read(cx)
22894            .all_source_breakpoints(cx)
22895    });
22896
22897    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22898
22899    editor.update_in(cx, |editor, window, cx| {
22900        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22901        editor.move_to_end(&MoveToEnd, window, cx);
22902        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22903        // Not adding a log message to a standard breakpoint shouldn't remove it
22904        add_log_breakpoint_at_cursor(editor, "", window, cx);
22905    });
22906
22907    let breakpoints = editor.update(cx, |editor, cx| {
22908        editor
22909            .breakpoint_store()
22910            .as_ref()
22911            .unwrap()
22912            .read(cx)
22913            .all_source_breakpoints(cx)
22914    });
22915
22916    assert_breakpoint(
22917        &breakpoints,
22918        &abs_path,
22919        vec![
22920            (0, Breakpoint::new_standard()),
22921            (3, Breakpoint::new_standard()),
22922        ],
22923    );
22924
22925    editor.update_in(cx, |editor, window, cx| {
22926        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22927    });
22928
22929    let breakpoints = editor.update(cx, |editor, cx| {
22930        editor
22931            .breakpoint_store()
22932            .as_ref()
22933            .unwrap()
22934            .read(cx)
22935            .all_source_breakpoints(cx)
22936    });
22937
22938    assert_breakpoint(
22939        &breakpoints,
22940        &abs_path,
22941        vec![
22942            (0, Breakpoint::new_standard()),
22943            (3, Breakpoint::new_log("hello world")),
22944        ],
22945    );
22946
22947    editor.update_in(cx, |editor, window, cx| {
22948        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22949    });
22950
22951    let breakpoints = editor.update(cx, |editor, cx| {
22952        editor
22953            .breakpoint_store()
22954            .as_ref()
22955            .unwrap()
22956            .read(cx)
22957            .all_source_breakpoints(cx)
22958    });
22959
22960    assert_breakpoint(
22961        &breakpoints,
22962        &abs_path,
22963        vec![
22964            (0, Breakpoint::new_standard()),
22965            (3, Breakpoint::new_log("hello Earth!!")),
22966        ],
22967    );
22968}
22969
22970/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22971/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22972/// or when breakpoints were placed out of order. This tests for a regression too
22973#[gpui::test]
22974async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22975    init_test(cx, |_| {});
22976
22977    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22978    let fs = FakeFs::new(cx.executor());
22979    fs.insert_tree(
22980        path!("/a"),
22981        json!({
22982            "main.rs": sample_text,
22983        }),
22984    )
22985    .await;
22986    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22987    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22988    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22989
22990    let fs = FakeFs::new(cx.executor());
22991    fs.insert_tree(
22992        path!("/a"),
22993        json!({
22994            "main.rs": sample_text,
22995        }),
22996    )
22997    .await;
22998    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22999    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23000    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23001    let worktree_id = workspace
23002        .update(cx, |workspace, _window, cx| {
23003            workspace.project().update(cx, |project, cx| {
23004                project.worktrees(cx).next().unwrap().read(cx).id()
23005            })
23006        })
23007        .unwrap();
23008
23009    let buffer = project
23010        .update(cx, |project, cx| {
23011            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23012        })
23013        .await
23014        .unwrap();
23015
23016    let (editor, cx) = cx.add_window_view(|window, cx| {
23017        Editor::new(
23018            EditorMode::full(),
23019            MultiBuffer::build_from_buffer(buffer, cx),
23020            Some(project.clone()),
23021            window,
23022            cx,
23023        )
23024    });
23025
23026    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23027    let abs_path = project.read_with(cx, |project, cx| {
23028        project
23029            .absolute_path(&project_path, cx)
23030            .map(Arc::from)
23031            .unwrap()
23032    });
23033
23034    // assert we can add breakpoint on the first line
23035    editor.update_in(cx, |editor, window, cx| {
23036        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23037        editor.move_to_end(&MoveToEnd, window, cx);
23038        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23039        editor.move_up(&MoveUp, window, cx);
23040        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23041    });
23042
23043    let breakpoints = editor.update(cx, |editor, cx| {
23044        editor
23045            .breakpoint_store()
23046            .as_ref()
23047            .unwrap()
23048            .read(cx)
23049            .all_source_breakpoints(cx)
23050    });
23051
23052    assert_eq!(1, breakpoints.len());
23053    assert_breakpoint(
23054        &breakpoints,
23055        &abs_path,
23056        vec![
23057            (0, Breakpoint::new_standard()),
23058            (2, Breakpoint::new_standard()),
23059            (3, Breakpoint::new_standard()),
23060        ],
23061    );
23062
23063    editor.update_in(cx, |editor, window, cx| {
23064        editor.move_to_beginning(&MoveToBeginning, window, cx);
23065        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23066        editor.move_to_end(&MoveToEnd, window, cx);
23067        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23068        // Disabling a breakpoint that doesn't exist should do nothing
23069        editor.move_up(&MoveUp, window, cx);
23070        editor.move_up(&MoveUp, window, cx);
23071        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23072    });
23073
23074    let breakpoints = editor.update(cx, |editor, cx| {
23075        editor
23076            .breakpoint_store()
23077            .as_ref()
23078            .unwrap()
23079            .read(cx)
23080            .all_source_breakpoints(cx)
23081    });
23082
23083    let disable_breakpoint = {
23084        let mut bp = Breakpoint::new_standard();
23085        bp.state = BreakpointState::Disabled;
23086        bp
23087    };
23088
23089    assert_eq!(1, breakpoints.len());
23090    assert_breakpoint(
23091        &breakpoints,
23092        &abs_path,
23093        vec![
23094            (0, disable_breakpoint.clone()),
23095            (2, Breakpoint::new_standard()),
23096            (3, disable_breakpoint.clone()),
23097        ],
23098    );
23099
23100    editor.update_in(cx, |editor, window, cx| {
23101        editor.move_to_beginning(&MoveToBeginning, window, cx);
23102        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23103        editor.move_to_end(&MoveToEnd, window, cx);
23104        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23105        editor.move_up(&MoveUp, window, cx);
23106        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23107    });
23108
23109    let breakpoints = editor.update(cx, |editor, cx| {
23110        editor
23111            .breakpoint_store()
23112            .as_ref()
23113            .unwrap()
23114            .read(cx)
23115            .all_source_breakpoints(cx)
23116    });
23117
23118    assert_eq!(1, breakpoints.len());
23119    assert_breakpoint(
23120        &breakpoints,
23121        &abs_path,
23122        vec![
23123            (0, Breakpoint::new_standard()),
23124            (2, disable_breakpoint),
23125            (3, Breakpoint::new_standard()),
23126        ],
23127    );
23128}
23129
23130#[gpui::test]
23131async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23132    init_test(cx, |_| {});
23133    let capabilities = lsp::ServerCapabilities {
23134        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23135            prepare_provider: Some(true),
23136            work_done_progress_options: Default::default(),
23137        })),
23138        ..Default::default()
23139    };
23140    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23141
23142    cx.set_state(indoc! {"
23143        struct Fˇoo {}
23144    "});
23145
23146    cx.update_editor(|editor, _, cx| {
23147        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23148        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23149        editor.highlight_background::<DocumentHighlightRead>(
23150            &[highlight_range],
23151            |theme| theme.colors().editor_document_highlight_read_background,
23152            cx,
23153        );
23154    });
23155
23156    let mut prepare_rename_handler = cx
23157        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23158            move |_, _, _| async move {
23159                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23160                    start: lsp::Position {
23161                        line: 0,
23162                        character: 7,
23163                    },
23164                    end: lsp::Position {
23165                        line: 0,
23166                        character: 10,
23167                    },
23168                })))
23169            },
23170        );
23171    let prepare_rename_task = cx
23172        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23173        .expect("Prepare rename was not started");
23174    prepare_rename_handler.next().await.unwrap();
23175    prepare_rename_task.await.expect("Prepare rename failed");
23176
23177    let mut rename_handler =
23178        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23179            let edit = lsp::TextEdit {
23180                range: lsp::Range {
23181                    start: lsp::Position {
23182                        line: 0,
23183                        character: 7,
23184                    },
23185                    end: lsp::Position {
23186                        line: 0,
23187                        character: 10,
23188                    },
23189                },
23190                new_text: "FooRenamed".to_string(),
23191            };
23192            Ok(Some(lsp::WorkspaceEdit::new(
23193                // Specify the same edit twice
23194                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23195            )))
23196        });
23197    let rename_task = cx
23198        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23199        .expect("Confirm rename was not started");
23200    rename_handler.next().await.unwrap();
23201    rename_task.await.expect("Confirm rename failed");
23202    cx.run_until_parked();
23203
23204    // Despite two edits, only one is actually applied as those are identical
23205    cx.assert_editor_state(indoc! {"
23206        struct FooRenamedˇ {}
23207    "});
23208}
23209
23210#[gpui::test]
23211async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23212    init_test(cx, |_| {});
23213    // These capabilities indicate that the server does not support prepare rename.
23214    let capabilities = lsp::ServerCapabilities {
23215        rename_provider: Some(lsp::OneOf::Left(true)),
23216        ..Default::default()
23217    };
23218    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23219
23220    cx.set_state(indoc! {"
23221        struct Fˇoo {}
23222    "});
23223
23224    cx.update_editor(|editor, _window, cx| {
23225        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23226        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23227        editor.highlight_background::<DocumentHighlightRead>(
23228            &[highlight_range],
23229            |theme| theme.colors().editor_document_highlight_read_background,
23230            cx,
23231        );
23232    });
23233
23234    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23235        .expect("Prepare rename was not started")
23236        .await
23237        .expect("Prepare rename failed");
23238
23239    let mut rename_handler =
23240        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23241            let edit = lsp::TextEdit {
23242                range: lsp::Range {
23243                    start: lsp::Position {
23244                        line: 0,
23245                        character: 7,
23246                    },
23247                    end: lsp::Position {
23248                        line: 0,
23249                        character: 10,
23250                    },
23251                },
23252                new_text: "FooRenamed".to_string(),
23253            };
23254            Ok(Some(lsp::WorkspaceEdit::new(
23255                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23256            )))
23257        });
23258    let rename_task = cx
23259        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23260        .expect("Confirm rename was not started");
23261    rename_handler.next().await.unwrap();
23262    rename_task.await.expect("Confirm rename failed");
23263    cx.run_until_parked();
23264
23265    // Correct range is renamed, as `surrounding_word` is used to find it.
23266    cx.assert_editor_state(indoc! {"
23267        struct FooRenamedˇ {}
23268    "});
23269}
23270
23271#[gpui::test]
23272async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23273    init_test(cx, |_| {});
23274    let mut cx = EditorTestContext::new(cx).await;
23275
23276    let language = Arc::new(
23277        Language::new(
23278            LanguageConfig::default(),
23279            Some(tree_sitter_html::LANGUAGE.into()),
23280        )
23281        .with_brackets_query(
23282            r#"
23283            ("<" @open "/>" @close)
23284            ("</" @open ">" @close)
23285            ("<" @open ">" @close)
23286            ("\"" @open "\"" @close)
23287            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23288        "#,
23289        )
23290        .unwrap(),
23291    );
23292    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23293
23294    cx.set_state(indoc! {"
23295        <span>ˇ</span>
23296    "});
23297    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23298    cx.assert_editor_state(indoc! {"
23299        <span>
23300        ˇ
23301        </span>
23302    "});
23303
23304    cx.set_state(indoc! {"
23305        <span><span></span>ˇ</span>
23306    "});
23307    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23308    cx.assert_editor_state(indoc! {"
23309        <span><span></span>
23310        ˇ</span>
23311    "});
23312
23313    cx.set_state(indoc! {"
23314        <span>ˇ
23315        </span>
23316    "});
23317    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23318    cx.assert_editor_state(indoc! {"
23319        <span>
23320        ˇ
23321        </span>
23322    "});
23323}
23324
23325#[gpui::test(iterations = 10)]
23326async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23327    init_test(cx, |_| {});
23328
23329    let fs = FakeFs::new(cx.executor());
23330    fs.insert_tree(
23331        path!("/dir"),
23332        json!({
23333            "a.ts": "a",
23334        }),
23335    )
23336    .await;
23337
23338    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23339    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23340    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23341
23342    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23343    language_registry.add(Arc::new(Language::new(
23344        LanguageConfig {
23345            name: "TypeScript".into(),
23346            matcher: LanguageMatcher {
23347                path_suffixes: vec!["ts".to_string()],
23348                ..Default::default()
23349            },
23350            ..Default::default()
23351        },
23352        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23353    )));
23354    let mut fake_language_servers = language_registry.register_fake_lsp(
23355        "TypeScript",
23356        FakeLspAdapter {
23357            capabilities: lsp::ServerCapabilities {
23358                code_lens_provider: Some(lsp::CodeLensOptions {
23359                    resolve_provider: Some(true),
23360                }),
23361                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23362                    commands: vec!["_the/command".to_string()],
23363                    ..lsp::ExecuteCommandOptions::default()
23364                }),
23365                ..lsp::ServerCapabilities::default()
23366            },
23367            ..FakeLspAdapter::default()
23368        },
23369    );
23370
23371    let editor = workspace
23372        .update(cx, |workspace, window, cx| {
23373            workspace.open_abs_path(
23374                PathBuf::from(path!("/dir/a.ts")),
23375                OpenOptions::default(),
23376                window,
23377                cx,
23378            )
23379        })
23380        .unwrap()
23381        .await
23382        .unwrap()
23383        .downcast::<Editor>()
23384        .unwrap();
23385    cx.executor().run_until_parked();
23386
23387    let fake_server = fake_language_servers.next().await.unwrap();
23388
23389    let buffer = editor.update(cx, |editor, cx| {
23390        editor
23391            .buffer()
23392            .read(cx)
23393            .as_singleton()
23394            .expect("have opened a single file by path")
23395    });
23396
23397    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23398    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23399    drop(buffer_snapshot);
23400    let actions = cx
23401        .update_window(*workspace, |_, window, cx| {
23402            project.code_actions(&buffer, anchor..anchor, window, cx)
23403        })
23404        .unwrap();
23405
23406    fake_server
23407        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23408            Ok(Some(vec![
23409                lsp::CodeLens {
23410                    range: lsp::Range::default(),
23411                    command: Some(lsp::Command {
23412                        title: "Code lens command".to_owned(),
23413                        command: "_the/command".to_owned(),
23414                        arguments: None,
23415                    }),
23416                    data: None,
23417                },
23418                lsp::CodeLens {
23419                    range: lsp::Range::default(),
23420                    command: Some(lsp::Command {
23421                        title: "Command not in capabilities".to_owned(),
23422                        command: "not in capabilities".to_owned(),
23423                        arguments: None,
23424                    }),
23425                    data: None,
23426                },
23427                lsp::CodeLens {
23428                    range: lsp::Range {
23429                        start: lsp::Position {
23430                            line: 1,
23431                            character: 1,
23432                        },
23433                        end: lsp::Position {
23434                            line: 1,
23435                            character: 1,
23436                        },
23437                    },
23438                    command: Some(lsp::Command {
23439                        title: "Command not in range".to_owned(),
23440                        command: "_the/command".to_owned(),
23441                        arguments: None,
23442                    }),
23443                    data: None,
23444                },
23445            ]))
23446        })
23447        .next()
23448        .await;
23449
23450    let actions = actions.await.unwrap();
23451    assert_eq!(
23452        actions.len(),
23453        1,
23454        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23455    );
23456    let action = actions[0].clone();
23457    let apply = project.update(cx, |project, cx| {
23458        project.apply_code_action(buffer.clone(), action, true, cx)
23459    });
23460
23461    // Resolving the code action does not populate its edits. In absence of
23462    // edits, we must execute the given command.
23463    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23464        |mut lens, _| async move {
23465            let lens_command = lens.command.as_mut().expect("should have a command");
23466            assert_eq!(lens_command.title, "Code lens command");
23467            lens_command.arguments = Some(vec![json!("the-argument")]);
23468            Ok(lens)
23469        },
23470    );
23471
23472    // While executing the command, the language server sends the editor
23473    // a `workspaceEdit` request.
23474    fake_server
23475        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23476            let fake = fake_server.clone();
23477            move |params, _| {
23478                assert_eq!(params.command, "_the/command");
23479                let fake = fake.clone();
23480                async move {
23481                    fake.server
23482                        .request::<lsp::request::ApplyWorkspaceEdit>(
23483                            lsp::ApplyWorkspaceEditParams {
23484                                label: None,
23485                                edit: lsp::WorkspaceEdit {
23486                                    changes: Some(
23487                                        [(
23488                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23489                                            vec![lsp::TextEdit {
23490                                                range: lsp::Range::new(
23491                                                    lsp::Position::new(0, 0),
23492                                                    lsp::Position::new(0, 0),
23493                                                ),
23494                                                new_text: "X".into(),
23495                                            }],
23496                                        )]
23497                                        .into_iter()
23498                                        .collect(),
23499                                    ),
23500                                    ..lsp::WorkspaceEdit::default()
23501                                },
23502                            },
23503                        )
23504                        .await
23505                        .into_response()
23506                        .unwrap();
23507                    Ok(Some(json!(null)))
23508                }
23509            }
23510        })
23511        .next()
23512        .await;
23513
23514    // Applying the code lens command returns a project transaction containing the edits
23515    // sent by the language server in its `workspaceEdit` request.
23516    let transaction = apply.await.unwrap();
23517    assert!(transaction.0.contains_key(&buffer));
23518    buffer.update(cx, |buffer, cx| {
23519        assert_eq!(buffer.text(), "Xa");
23520        buffer.undo(cx);
23521        assert_eq!(buffer.text(), "a");
23522    });
23523
23524    let actions_after_edits = cx
23525        .update_window(*workspace, |_, window, cx| {
23526            project.code_actions(&buffer, anchor..anchor, window, cx)
23527        })
23528        .unwrap()
23529        .await
23530        .unwrap();
23531    assert_eq!(
23532        actions, actions_after_edits,
23533        "For the same selection, same code lens actions should be returned"
23534    );
23535
23536    let _responses =
23537        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23538            panic!("No more code lens requests are expected");
23539        });
23540    editor.update_in(cx, |editor, window, cx| {
23541        editor.select_all(&SelectAll, window, cx);
23542    });
23543    cx.executor().run_until_parked();
23544    let new_actions = cx
23545        .update_window(*workspace, |_, window, cx| {
23546            project.code_actions(&buffer, anchor..anchor, window, cx)
23547        })
23548        .unwrap()
23549        .await
23550        .unwrap();
23551    assert_eq!(
23552        actions, new_actions,
23553        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23554    );
23555}
23556
23557#[gpui::test]
23558async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23559    init_test(cx, |_| {});
23560
23561    let fs = FakeFs::new(cx.executor());
23562    let main_text = r#"fn main() {
23563println!("1");
23564println!("2");
23565println!("3");
23566println!("4");
23567println!("5");
23568}"#;
23569    let lib_text = "mod foo {}";
23570    fs.insert_tree(
23571        path!("/a"),
23572        json!({
23573            "lib.rs": lib_text,
23574            "main.rs": main_text,
23575        }),
23576    )
23577    .await;
23578
23579    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23580    let (workspace, cx) =
23581        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23582    let worktree_id = workspace.update(cx, |workspace, cx| {
23583        workspace.project().update(cx, |project, cx| {
23584            project.worktrees(cx).next().unwrap().read(cx).id()
23585        })
23586    });
23587
23588    let expected_ranges = vec![
23589        Point::new(0, 0)..Point::new(0, 0),
23590        Point::new(1, 0)..Point::new(1, 1),
23591        Point::new(2, 0)..Point::new(2, 2),
23592        Point::new(3, 0)..Point::new(3, 3),
23593    ];
23594
23595    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23596    let editor_1 = workspace
23597        .update_in(cx, |workspace, window, cx| {
23598            workspace.open_path(
23599                (worktree_id, rel_path("main.rs")),
23600                Some(pane_1.downgrade()),
23601                true,
23602                window,
23603                cx,
23604            )
23605        })
23606        .unwrap()
23607        .await
23608        .downcast::<Editor>()
23609        .unwrap();
23610    pane_1.update(cx, |pane, cx| {
23611        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23612        open_editor.update(cx, |editor, cx| {
23613            assert_eq!(
23614                editor.display_text(cx),
23615                main_text,
23616                "Original main.rs text on initial open",
23617            );
23618            assert_eq!(
23619                editor
23620                    .selections
23621                    .all::<Point>(cx)
23622                    .into_iter()
23623                    .map(|s| s.range())
23624                    .collect::<Vec<_>>(),
23625                vec![Point::zero()..Point::zero()],
23626                "Default selections on initial open",
23627            );
23628        })
23629    });
23630    editor_1.update_in(cx, |editor, window, cx| {
23631        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23632            s.select_ranges(expected_ranges.clone());
23633        });
23634    });
23635
23636    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23637        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23638    });
23639    let editor_2 = workspace
23640        .update_in(cx, |workspace, window, cx| {
23641            workspace.open_path(
23642                (worktree_id, rel_path("main.rs")),
23643                Some(pane_2.downgrade()),
23644                true,
23645                window,
23646                cx,
23647            )
23648        })
23649        .unwrap()
23650        .await
23651        .downcast::<Editor>()
23652        .unwrap();
23653    pane_2.update(cx, |pane, cx| {
23654        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23655        open_editor.update(cx, |editor, cx| {
23656            assert_eq!(
23657                editor.display_text(cx),
23658                main_text,
23659                "Original main.rs text on initial open in another panel",
23660            );
23661            assert_eq!(
23662                editor
23663                    .selections
23664                    .all::<Point>(cx)
23665                    .into_iter()
23666                    .map(|s| s.range())
23667                    .collect::<Vec<_>>(),
23668                vec![Point::zero()..Point::zero()],
23669                "Default selections on initial open in another panel",
23670            );
23671        })
23672    });
23673
23674    editor_2.update_in(cx, |editor, window, cx| {
23675        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23676    });
23677
23678    let _other_editor_1 = workspace
23679        .update_in(cx, |workspace, window, cx| {
23680            workspace.open_path(
23681                (worktree_id, rel_path("lib.rs")),
23682                Some(pane_1.downgrade()),
23683                true,
23684                window,
23685                cx,
23686            )
23687        })
23688        .unwrap()
23689        .await
23690        .downcast::<Editor>()
23691        .unwrap();
23692    pane_1
23693        .update_in(cx, |pane, window, cx| {
23694            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23695        })
23696        .await
23697        .unwrap();
23698    drop(editor_1);
23699    pane_1.update(cx, |pane, cx| {
23700        pane.active_item()
23701            .unwrap()
23702            .downcast::<Editor>()
23703            .unwrap()
23704            .update(cx, |editor, cx| {
23705                assert_eq!(
23706                    editor.display_text(cx),
23707                    lib_text,
23708                    "Other file should be open and active",
23709                );
23710            });
23711        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23712    });
23713
23714    let _other_editor_2 = workspace
23715        .update_in(cx, |workspace, window, cx| {
23716            workspace.open_path(
23717                (worktree_id, rel_path("lib.rs")),
23718                Some(pane_2.downgrade()),
23719                true,
23720                window,
23721                cx,
23722            )
23723        })
23724        .unwrap()
23725        .await
23726        .downcast::<Editor>()
23727        .unwrap();
23728    pane_2
23729        .update_in(cx, |pane, window, cx| {
23730            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23731        })
23732        .await
23733        .unwrap();
23734    drop(editor_2);
23735    pane_2.update(cx, |pane, cx| {
23736        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23737        open_editor.update(cx, |editor, cx| {
23738            assert_eq!(
23739                editor.display_text(cx),
23740                lib_text,
23741                "Other file should be open and active in another panel too",
23742            );
23743        });
23744        assert_eq!(
23745            pane.items().count(),
23746            1,
23747            "No other editors should be open in another pane",
23748        );
23749    });
23750
23751    let _editor_1_reopened = workspace
23752        .update_in(cx, |workspace, window, cx| {
23753            workspace.open_path(
23754                (worktree_id, rel_path("main.rs")),
23755                Some(pane_1.downgrade()),
23756                true,
23757                window,
23758                cx,
23759            )
23760        })
23761        .unwrap()
23762        .await
23763        .downcast::<Editor>()
23764        .unwrap();
23765    let _editor_2_reopened = workspace
23766        .update_in(cx, |workspace, window, cx| {
23767            workspace.open_path(
23768                (worktree_id, rel_path("main.rs")),
23769                Some(pane_2.downgrade()),
23770                true,
23771                window,
23772                cx,
23773            )
23774        })
23775        .unwrap()
23776        .await
23777        .downcast::<Editor>()
23778        .unwrap();
23779    pane_1.update(cx, |pane, cx| {
23780        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23781        open_editor.update(cx, |editor, cx| {
23782            assert_eq!(
23783                editor.display_text(cx),
23784                main_text,
23785                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23786            );
23787            assert_eq!(
23788                editor
23789                    .selections
23790                    .all::<Point>(cx)
23791                    .into_iter()
23792                    .map(|s| s.range())
23793                    .collect::<Vec<_>>(),
23794                expected_ranges,
23795                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23796            );
23797        })
23798    });
23799    pane_2.update(cx, |pane, cx| {
23800        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23801        open_editor.update(cx, |editor, cx| {
23802            assert_eq!(
23803                editor.display_text(cx),
23804                r#"fn main() {
23805⋯rintln!("1");
23806⋯intln!("2");
23807⋯ntln!("3");
23808println!("4");
23809println!("5");
23810}"#,
23811                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23812            );
23813            assert_eq!(
23814                editor
23815                    .selections
23816                    .all::<Point>(cx)
23817                    .into_iter()
23818                    .map(|s| s.range())
23819                    .collect::<Vec<_>>(),
23820                vec![Point::zero()..Point::zero()],
23821                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23822            );
23823        })
23824    });
23825}
23826
23827#[gpui::test]
23828async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23829    init_test(cx, |_| {});
23830
23831    let fs = FakeFs::new(cx.executor());
23832    let main_text = r#"fn main() {
23833println!("1");
23834println!("2");
23835println!("3");
23836println!("4");
23837println!("5");
23838}"#;
23839    let lib_text = "mod foo {}";
23840    fs.insert_tree(
23841        path!("/a"),
23842        json!({
23843            "lib.rs": lib_text,
23844            "main.rs": main_text,
23845        }),
23846    )
23847    .await;
23848
23849    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23850    let (workspace, cx) =
23851        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23852    let worktree_id = workspace.update(cx, |workspace, cx| {
23853        workspace.project().update(cx, |project, cx| {
23854            project.worktrees(cx).next().unwrap().read(cx).id()
23855        })
23856    });
23857
23858    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23859    let editor = workspace
23860        .update_in(cx, |workspace, window, cx| {
23861            workspace.open_path(
23862                (worktree_id, rel_path("main.rs")),
23863                Some(pane.downgrade()),
23864                true,
23865                window,
23866                cx,
23867            )
23868        })
23869        .unwrap()
23870        .await
23871        .downcast::<Editor>()
23872        .unwrap();
23873    pane.update(cx, |pane, cx| {
23874        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23875        open_editor.update(cx, |editor, cx| {
23876            assert_eq!(
23877                editor.display_text(cx),
23878                main_text,
23879                "Original main.rs text on initial open",
23880            );
23881        })
23882    });
23883    editor.update_in(cx, |editor, window, cx| {
23884        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23885    });
23886
23887    cx.update_global(|store: &mut SettingsStore, cx| {
23888        store.update_user_settings(cx, |s| {
23889            s.workspace.restore_on_file_reopen = Some(false);
23890        });
23891    });
23892    editor.update_in(cx, |editor, window, cx| {
23893        editor.fold_ranges(
23894            vec![
23895                Point::new(1, 0)..Point::new(1, 1),
23896                Point::new(2, 0)..Point::new(2, 2),
23897                Point::new(3, 0)..Point::new(3, 3),
23898            ],
23899            false,
23900            window,
23901            cx,
23902        );
23903    });
23904    pane.update_in(cx, |pane, window, cx| {
23905        pane.close_all_items(&CloseAllItems::default(), window, cx)
23906    })
23907    .await
23908    .unwrap();
23909    pane.update(cx, |pane, _| {
23910        assert!(pane.active_item().is_none());
23911    });
23912    cx.update_global(|store: &mut SettingsStore, cx| {
23913        store.update_user_settings(cx, |s| {
23914            s.workspace.restore_on_file_reopen = Some(true);
23915        });
23916    });
23917
23918    let _editor_reopened = workspace
23919        .update_in(cx, |workspace, window, cx| {
23920            workspace.open_path(
23921                (worktree_id, rel_path("main.rs")),
23922                Some(pane.downgrade()),
23923                true,
23924                window,
23925                cx,
23926            )
23927        })
23928        .unwrap()
23929        .await
23930        .downcast::<Editor>()
23931        .unwrap();
23932    pane.update(cx, |pane, cx| {
23933        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23934        open_editor.update(cx, |editor, cx| {
23935            assert_eq!(
23936                editor.display_text(cx),
23937                main_text,
23938                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23939            );
23940        })
23941    });
23942}
23943
23944#[gpui::test]
23945async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23946    struct EmptyModalView {
23947        focus_handle: gpui::FocusHandle,
23948    }
23949    impl EventEmitter<DismissEvent> for EmptyModalView {}
23950    impl Render for EmptyModalView {
23951        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23952            div()
23953        }
23954    }
23955    impl Focusable for EmptyModalView {
23956        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23957            self.focus_handle.clone()
23958        }
23959    }
23960    impl workspace::ModalView for EmptyModalView {}
23961    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23962        EmptyModalView {
23963            focus_handle: cx.focus_handle(),
23964        }
23965    }
23966
23967    init_test(cx, |_| {});
23968
23969    let fs = FakeFs::new(cx.executor());
23970    let project = Project::test(fs, [], cx).await;
23971    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23972    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23973    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23974    let editor = cx.new_window_entity(|window, cx| {
23975        Editor::new(
23976            EditorMode::full(),
23977            buffer,
23978            Some(project.clone()),
23979            window,
23980            cx,
23981        )
23982    });
23983    workspace
23984        .update(cx, |workspace, window, cx| {
23985            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23986        })
23987        .unwrap();
23988    editor.update_in(cx, |editor, window, cx| {
23989        editor.open_context_menu(&OpenContextMenu, window, cx);
23990        assert!(editor.mouse_context_menu.is_some());
23991    });
23992    workspace
23993        .update(cx, |workspace, window, cx| {
23994            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23995        })
23996        .unwrap();
23997    cx.read(|cx| {
23998        assert!(editor.read(cx).mouse_context_menu.is_none());
23999    });
24000}
24001
24002fn set_linked_edit_ranges(
24003    opening: (Point, Point),
24004    closing: (Point, Point),
24005    editor: &mut Editor,
24006    cx: &mut Context<Editor>,
24007) {
24008    let Some((buffer, _)) = editor
24009        .buffer
24010        .read(cx)
24011        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24012    else {
24013        panic!("Failed to get buffer for selection position");
24014    };
24015    let buffer = buffer.read(cx);
24016    let buffer_id = buffer.remote_id();
24017    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24018    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24019    let mut linked_ranges = HashMap::default();
24020    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24021    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24022}
24023
24024#[gpui::test]
24025async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24026    init_test(cx, |_| {});
24027
24028    let fs = FakeFs::new(cx.executor());
24029    fs.insert_file(path!("/file.html"), Default::default())
24030        .await;
24031
24032    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24033
24034    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24035    let html_language = Arc::new(Language::new(
24036        LanguageConfig {
24037            name: "HTML".into(),
24038            matcher: LanguageMatcher {
24039                path_suffixes: vec!["html".to_string()],
24040                ..LanguageMatcher::default()
24041            },
24042            brackets: BracketPairConfig {
24043                pairs: vec![BracketPair {
24044                    start: "<".into(),
24045                    end: ">".into(),
24046                    close: true,
24047                    ..Default::default()
24048                }],
24049                ..Default::default()
24050            },
24051            ..Default::default()
24052        },
24053        Some(tree_sitter_html::LANGUAGE.into()),
24054    ));
24055    language_registry.add(html_language);
24056    let mut fake_servers = language_registry.register_fake_lsp(
24057        "HTML",
24058        FakeLspAdapter {
24059            capabilities: lsp::ServerCapabilities {
24060                completion_provider: Some(lsp::CompletionOptions {
24061                    resolve_provider: Some(true),
24062                    ..Default::default()
24063                }),
24064                ..Default::default()
24065            },
24066            ..Default::default()
24067        },
24068    );
24069
24070    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24071    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24072
24073    let worktree_id = workspace
24074        .update(cx, |workspace, _window, cx| {
24075            workspace.project().update(cx, |project, cx| {
24076                project.worktrees(cx).next().unwrap().read(cx).id()
24077            })
24078        })
24079        .unwrap();
24080    project
24081        .update(cx, |project, cx| {
24082            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24083        })
24084        .await
24085        .unwrap();
24086    let editor = workspace
24087        .update(cx, |workspace, window, cx| {
24088            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24089        })
24090        .unwrap()
24091        .await
24092        .unwrap()
24093        .downcast::<Editor>()
24094        .unwrap();
24095
24096    let fake_server = fake_servers.next().await.unwrap();
24097    editor.update_in(cx, |editor, window, cx| {
24098        editor.set_text("<ad></ad>", window, cx);
24099        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24100            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24101        });
24102        set_linked_edit_ranges(
24103            (Point::new(0, 1), Point::new(0, 3)),
24104            (Point::new(0, 6), Point::new(0, 8)),
24105            editor,
24106            cx,
24107        );
24108    });
24109    let mut completion_handle =
24110        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24111            Ok(Some(lsp::CompletionResponse::Array(vec![
24112                lsp::CompletionItem {
24113                    label: "head".to_string(),
24114                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24115                        lsp::InsertReplaceEdit {
24116                            new_text: "head".to_string(),
24117                            insert: lsp::Range::new(
24118                                lsp::Position::new(0, 1),
24119                                lsp::Position::new(0, 3),
24120                            ),
24121                            replace: lsp::Range::new(
24122                                lsp::Position::new(0, 1),
24123                                lsp::Position::new(0, 3),
24124                            ),
24125                        },
24126                    )),
24127                    ..Default::default()
24128                },
24129            ])))
24130        });
24131    editor.update_in(cx, |editor, window, cx| {
24132        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
24133    });
24134    cx.run_until_parked();
24135    completion_handle.next().await.unwrap();
24136    editor.update(cx, |editor, _| {
24137        assert!(
24138            editor.context_menu_visible(),
24139            "Completion menu should be visible"
24140        );
24141    });
24142    editor.update_in(cx, |editor, window, cx| {
24143        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24144    });
24145    cx.executor().run_until_parked();
24146    editor.update(cx, |editor, cx| {
24147        assert_eq!(editor.text(cx), "<head></head>");
24148    });
24149}
24150
24151#[gpui::test]
24152async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24153    init_test(cx, |_| {});
24154
24155    let mut cx = EditorTestContext::new(cx).await;
24156    let language = Arc::new(Language::new(
24157        LanguageConfig {
24158            name: "TSX".into(),
24159            matcher: LanguageMatcher {
24160                path_suffixes: vec!["tsx".to_string()],
24161                ..LanguageMatcher::default()
24162            },
24163            brackets: BracketPairConfig {
24164                pairs: vec![BracketPair {
24165                    start: "<".into(),
24166                    end: ">".into(),
24167                    close: true,
24168                    ..Default::default()
24169                }],
24170                ..Default::default()
24171            },
24172            linked_edit_characters: HashSet::from_iter(['.']),
24173            ..Default::default()
24174        },
24175        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24176    ));
24177    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24178
24179    // Test typing > does not extend linked pair
24180    cx.set_state("<divˇ<div></div>");
24181    cx.update_editor(|editor, _, cx| {
24182        set_linked_edit_ranges(
24183            (Point::new(0, 1), Point::new(0, 4)),
24184            (Point::new(0, 11), Point::new(0, 14)),
24185            editor,
24186            cx,
24187        );
24188    });
24189    cx.update_editor(|editor, window, cx| {
24190        editor.handle_input(">", window, cx);
24191    });
24192    cx.assert_editor_state("<div>ˇ<div></div>");
24193
24194    // Test typing . do extend linked pair
24195    cx.set_state("<Animatedˇ></Animated>");
24196    cx.update_editor(|editor, _, cx| {
24197        set_linked_edit_ranges(
24198            (Point::new(0, 1), Point::new(0, 9)),
24199            (Point::new(0, 12), Point::new(0, 20)),
24200            editor,
24201            cx,
24202        );
24203    });
24204    cx.update_editor(|editor, window, cx| {
24205        editor.handle_input(".", window, cx);
24206    });
24207    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24208    cx.update_editor(|editor, _, cx| {
24209        set_linked_edit_ranges(
24210            (Point::new(0, 1), Point::new(0, 10)),
24211            (Point::new(0, 13), Point::new(0, 21)),
24212            editor,
24213            cx,
24214        );
24215    });
24216    cx.update_editor(|editor, window, cx| {
24217        editor.handle_input("V", window, cx);
24218    });
24219    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24220}
24221
24222#[gpui::test]
24223async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24224    init_test(cx, |_| {});
24225
24226    let fs = FakeFs::new(cx.executor());
24227    fs.insert_tree(
24228        path!("/root"),
24229        json!({
24230            "a": {
24231                "main.rs": "fn main() {}",
24232            },
24233            "foo": {
24234                "bar": {
24235                    "external_file.rs": "pub mod external {}",
24236                }
24237            }
24238        }),
24239    )
24240    .await;
24241
24242    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24243    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24244    language_registry.add(rust_lang());
24245    let _fake_servers = language_registry.register_fake_lsp(
24246        "Rust",
24247        FakeLspAdapter {
24248            ..FakeLspAdapter::default()
24249        },
24250    );
24251    let (workspace, cx) =
24252        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24253    let worktree_id = workspace.update(cx, |workspace, cx| {
24254        workspace.project().update(cx, |project, cx| {
24255            project.worktrees(cx).next().unwrap().read(cx).id()
24256        })
24257    });
24258
24259    let assert_language_servers_count =
24260        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24261            project.update(cx, |project, cx| {
24262                let current = project
24263                    .lsp_store()
24264                    .read(cx)
24265                    .as_local()
24266                    .unwrap()
24267                    .language_servers
24268                    .len();
24269                assert_eq!(expected, current, "{context}");
24270            });
24271        };
24272
24273    assert_language_servers_count(
24274        0,
24275        "No servers should be running before any file is open",
24276        cx,
24277    );
24278    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24279    let main_editor = workspace
24280        .update_in(cx, |workspace, window, cx| {
24281            workspace.open_path(
24282                (worktree_id, rel_path("main.rs")),
24283                Some(pane.downgrade()),
24284                true,
24285                window,
24286                cx,
24287            )
24288        })
24289        .unwrap()
24290        .await
24291        .downcast::<Editor>()
24292        .unwrap();
24293    pane.update(cx, |pane, cx| {
24294        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24295        open_editor.update(cx, |editor, cx| {
24296            assert_eq!(
24297                editor.display_text(cx),
24298                "fn main() {}",
24299                "Original main.rs text on initial open",
24300            );
24301        });
24302        assert_eq!(open_editor, main_editor);
24303    });
24304    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24305
24306    let external_editor = workspace
24307        .update_in(cx, |workspace, window, cx| {
24308            workspace.open_abs_path(
24309                PathBuf::from("/root/foo/bar/external_file.rs"),
24310                OpenOptions::default(),
24311                window,
24312                cx,
24313            )
24314        })
24315        .await
24316        .expect("opening external file")
24317        .downcast::<Editor>()
24318        .expect("downcasted external file's open element to editor");
24319    pane.update(cx, |pane, cx| {
24320        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24321        open_editor.update(cx, |editor, cx| {
24322            assert_eq!(
24323                editor.display_text(cx),
24324                "pub mod external {}",
24325                "External file is open now",
24326            );
24327        });
24328        assert_eq!(open_editor, external_editor);
24329    });
24330    assert_language_servers_count(
24331        1,
24332        "Second, external, *.rs file should join the existing server",
24333        cx,
24334    );
24335
24336    pane.update_in(cx, |pane, window, cx| {
24337        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24338    })
24339    .await
24340    .unwrap();
24341    pane.update_in(cx, |pane, window, cx| {
24342        pane.navigate_backward(&Default::default(), window, cx);
24343    });
24344    cx.run_until_parked();
24345    pane.update(cx, |pane, cx| {
24346        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24347        open_editor.update(cx, |editor, cx| {
24348            assert_eq!(
24349                editor.display_text(cx),
24350                "pub mod external {}",
24351                "External file is open now",
24352            );
24353        });
24354    });
24355    assert_language_servers_count(
24356        1,
24357        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24358        cx,
24359    );
24360
24361    cx.update(|_, cx| {
24362        workspace::reload(cx);
24363    });
24364    assert_language_servers_count(
24365        1,
24366        "After reloading the worktree with local and external files opened, only one project should be started",
24367        cx,
24368    );
24369}
24370
24371#[gpui::test]
24372async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24373    init_test(cx, |_| {});
24374
24375    let mut cx = EditorTestContext::new(cx).await;
24376    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24377    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24378
24379    // test cursor move to start of each line on tab
24380    // for `if`, `elif`, `else`, `while`, `with` and `for`
24381    cx.set_state(indoc! {"
24382        def main():
24383        ˇ    for item in items:
24384        ˇ        while item.active:
24385        ˇ            if item.value > 10:
24386        ˇ                continue
24387        ˇ            elif item.value < 0:
24388        ˇ                break
24389        ˇ            else:
24390        ˇ                with item.context() as ctx:
24391        ˇ                    yield count
24392        ˇ        else:
24393        ˇ            log('while else')
24394        ˇ    else:
24395        ˇ        log('for else')
24396    "});
24397    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24398    cx.assert_editor_state(indoc! {"
24399        def main():
24400            ˇfor item in items:
24401                ˇwhile item.active:
24402                    ˇif item.value > 10:
24403                        ˇcontinue
24404                    ˇelif item.value < 0:
24405                        ˇbreak
24406                    ˇelse:
24407                        ˇwith item.context() as ctx:
24408                            ˇyield count
24409                ˇelse:
24410                    ˇlog('while else')
24411            ˇelse:
24412                ˇlog('for else')
24413    "});
24414    // test relative indent is preserved when tab
24415    // for `if`, `elif`, `else`, `while`, `with` and `for`
24416    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24417    cx.assert_editor_state(indoc! {"
24418        def main():
24419                ˇfor item in items:
24420                    ˇwhile item.active:
24421                        ˇif item.value > 10:
24422                            ˇcontinue
24423                        ˇelif item.value < 0:
24424                            ˇbreak
24425                        ˇelse:
24426                            ˇwith item.context() as ctx:
24427                                ˇyield count
24428                    ˇelse:
24429                        ˇlog('while else')
24430                ˇelse:
24431                    ˇlog('for else')
24432    "});
24433
24434    // test cursor move to start of each line on tab
24435    // for `try`, `except`, `else`, `finally`, `match` and `def`
24436    cx.set_state(indoc! {"
24437        def main():
24438        ˇ    try:
24439        ˇ        fetch()
24440        ˇ    except ValueError:
24441        ˇ        handle_error()
24442        ˇ    else:
24443        ˇ        match value:
24444        ˇ            case _:
24445        ˇ    finally:
24446        ˇ        def status():
24447        ˇ            return 0
24448    "});
24449    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24450    cx.assert_editor_state(indoc! {"
24451        def main():
24452            ˇtry:
24453                ˇfetch()
24454            ˇexcept ValueError:
24455                ˇhandle_error()
24456            ˇelse:
24457                ˇmatch value:
24458                    ˇcase _:
24459            ˇfinally:
24460                ˇdef status():
24461                    ˇreturn 0
24462    "});
24463    // test relative indent is preserved when tab
24464    // for `try`, `except`, `else`, `finally`, `match` and `def`
24465    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24466    cx.assert_editor_state(indoc! {"
24467        def main():
24468                ˇtry:
24469                    ˇfetch()
24470                ˇexcept ValueError:
24471                    ˇhandle_error()
24472                ˇelse:
24473                    ˇmatch value:
24474                        ˇcase _:
24475                ˇfinally:
24476                    ˇdef status():
24477                        ˇreturn 0
24478    "});
24479}
24480
24481#[gpui::test]
24482async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24483    init_test(cx, |_| {});
24484
24485    let mut cx = EditorTestContext::new(cx).await;
24486    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24487    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24488
24489    // test `else` auto outdents when typed inside `if` block
24490    cx.set_state(indoc! {"
24491        def main():
24492            if i == 2:
24493                return
24494                ˇ
24495    "});
24496    cx.update_editor(|editor, window, cx| {
24497        editor.handle_input("else:", window, cx);
24498    });
24499    cx.assert_editor_state(indoc! {"
24500        def main():
24501            if i == 2:
24502                return
24503            else:ˇ
24504    "});
24505
24506    // test `except` auto outdents when typed inside `try` block
24507    cx.set_state(indoc! {"
24508        def main():
24509            try:
24510                i = 2
24511                ˇ
24512    "});
24513    cx.update_editor(|editor, window, cx| {
24514        editor.handle_input("except:", window, cx);
24515    });
24516    cx.assert_editor_state(indoc! {"
24517        def main():
24518            try:
24519                i = 2
24520            except:ˇ
24521    "});
24522
24523    // test `else` auto outdents when typed inside `except` block
24524    cx.set_state(indoc! {"
24525        def main():
24526            try:
24527                i = 2
24528            except:
24529                j = 2
24530                ˇ
24531    "});
24532    cx.update_editor(|editor, window, cx| {
24533        editor.handle_input("else:", window, cx);
24534    });
24535    cx.assert_editor_state(indoc! {"
24536        def main():
24537            try:
24538                i = 2
24539            except:
24540                j = 2
24541            else:ˇ
24542    "});
24543
24544    // test `finally` auto outdents when typed inside `else` block
24545    cx.set_state(indoc! {"
24546        def main():
24547            try:
24548                i = 2
24549            except:
24550                j = 2
24551            else:
24552                k = 2
24553                ˇ
24554    "});
24555    cx.update_editor(|editor, window, cx| {
24556        editor.handle_input("finally:", window, cx);
24557    });
24558    cx.assert_editor_state(indoc! {"
24559        def main():
24560            try:
24561                i = 2
24562            except:
24563                j = 2
24564            else:
24565                k = 2
24566            finally:ˇ
24567    "});
24568
24569    // test `else` does not outdents when typed inside `except` block right after for block
24570    cx.set_state(indoc! {"
24571        def main():
24572            try:
24573                i = 2
24574            except:
24575                for i in range(n):
24576                    pass
24577                ˇ
24578    "});
24579    cx.update_editor(|editor, window, cx| {
24580        editor.handle_input("else:", window, cx);
24581    });
24582    cx.assert_editor_state(indoc! {"
24583        def main():
24584            try:
24585                i = 2
24586            except:
24587                for i in range(n):
24588                    pass
24589                else:ˇ
24590    "});
24591
24592    // test `finally` auto outdents when typed inside `else` block right after for block
24593    cx.set_state(indoc! {"
24594        def main():
24595            try:
24596                i = 2
24597            except:
24598                j = 2
24599            else:
24600                for i in range(n):
24601                    pass
24602                ˇ
24603    "});
24604    cx.update_editor(|editor, window, cx| {
24605        editor.handle_input("finally:", window, cx);
24606    });
24607    cx.assert_editor_state(indoc! {"
24608        def main():
24609            try:
24610                i = 2
24611            except:
24612                j = 2
24613            else:
24614                for i in range(n):
24615                    pass
24616            finally:ˇ
24617    "});
24618
24619    // test `except` outdents to inner "try" block
24620    cx.set_state(indoc! {"
24621        def main():
24622            try:
24623                i = 2
24624                if i == 2:
24625                    try:
24626                        i = 3
24627                        ˇ
24628    "});
24629    cx.update_editor(|editor, window, cx| {
24630        editor.handle_input("except:", window, cx);
24631    });
24632    cx.assert_editor_state(indoc! {"
24633        def main():
24634            try:
24635                i = 2
24636                if i == 2:
24637                    try:
24638                        i = 3
24639                    except:ˇ
24640    "});
24641
24642    // test `except` outdents to outer "try" block
24643    cx.set_state(indoc! {"
24644        def main():
24645            try:
24646                i = 2
24647                if i == 2:
24648                    try:
24649                        i = 3
24650                ˇ
24651    "});
24652    cx.update_editor(|editor, window, cx| {
24653        editor.handle_input("except:", window, cx);
24654    });
24655    cx.assert_editor_state(indoc! {"
24656        def main():
24657            try:
24658                i = 2
24659                if i == 2:
24660                    try:
24661                        i = 3
24662            except:ˇ
24663    "});
24664
24665    // test `else` stays at correct indent when typed after `for` block
24666    cx.set_state(indoc! {"
24667        def main():
24668            for i in range(10):
24669                if i == 3:
24670                    break
24671            ˇ
24672    "});
24673    cx.update_editor(|editor, window, cx| {
24674        editor.handle_input("else:", window, cx);
24675    });
24676    cx.assert_editor_state(indoc! {"
24677        def main():
24678            for i in range(10):
24679                if i == 3:
24680                    break
24681            else:ˇ
24682    "});
24683
24684    // test does not outdent on typing after line with square brackets
24685    cx.set_state(indoc! {"
24686        def f() -> list[str]:
24687            ˇ
24688    "});
24689    cx.update_editor(|editor, window, cx| {
24690        editor.handle_input("a", window, cx);
24691    });
24692    cx.assert_editor_state(indoc! {"
24693        def f() -> list[str]:
2469424695    "});
24696
24697    // test does not outdent on typing : after case keyword
24698    cx.set_state(indoc! {"
24699        match 1:
24700            caseˇ
24701    "});
24702    cx.update_editor(|editor, window, cx| {
24703        editor.handle_input(":", window, cx);
24704    });
24705    cx.assert_editor_state(indoc! {"
24706        match 1:
24707            case:ˇ
24708    "});
24709}
24710
24711#[gpui::test]
24712async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24713    init_test(cx, |_| {});
24714    update_test_language_settings(cx, |settings| {
24715        settings.defaults.extend_comment_on_newline = Some(false);
24716    });
24717    let mut cx = EditorTestContext::new(cx).await;
24718    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24719    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24720
24721    // test correct indent after newline on comment
24722    cx.set_state(indoc! {"
24723        # COMMENT:ˇ
24724    "});
24725    cx.update_editor(|editor, window, cx| {
24726        editor.newline(&Newline, window, cx);
24727    });
24728    cx.assert_editor_state(indoc! {"
24729        # COMMENT:
24730        ˇ
24731    "});
24732
24733    // test correct indent after newline in brackets
24734    cx.set_state(indoc! {"
24735        {ˇ}
24736    "});
24737    cx.update_editor(|editor, window, cx| {
24738        editor.newline(&Newline, window, cx);
24739    });
24740    cx.run_until_parked();
24741    cx.assert_editor_state(indoc! {"
24742        {
24743            ˇ
24744        }
24745    "});
24746
24747    cx.set_state(indoc! {"
24748        (ˇ)
24749    "});
24750    cx.update_editor(|editor, window, cx| {
24751        editor.newline(&Newline, window, cx);
24752    });
24753    cx.run_until_parked();
24754    cx.assert_editor_state(indoc! {"
24755        (
24756            ˇ
24757        )
24758    "});
24759
24760    // do not indent after empty lists or dictionaries
24761    cx.set_state(indoc! {"
24762        a = []ˇ
24763    "});
24764    cx.update_editor(|editor, window, cx| {
24765        editor.newline(&Newline, window, cx);
24766    });
24767    cx.run_until_parked();
24768    cx.assert_editor_state(indoc! {"
24769        a = []
24770        ˇ
24771    "});
24772}
24773
24774#[gpui::test]
24775async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24776    init_test(cx, |_| {});
24777
24778    let mut cx = EditorTestContext::new(cx).await;
24779    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24780    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24781
24782    // test cursor move to start of each line on tab
24783    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24784    cx.set_state(indoc! {"
24785        function main() {
24786        ˇ    for item in $items; do
24787        ˇ        while [ -n \"$item\" ]; do
24788        ˇ            if [ \"$value\" -gt 10 ]; then
24789        ˇ                continue
24790        ˇ            elif [ \"$value\" -lt 0 ]; then
24791        ˇ                break
24792        ˇ            else
24793        ˇ                echo \"$item\"
24794        ˇ            fi
24795        ˇ        done
24796        ˇ    done
24797        ˇ}
24798    "});
24799    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24800    cx.assert_editor_state(indoc! {"
24801        function main() {
24802            ˇfor item in $items; do
24803                ˇwhile [ -n \"$item\" ]; do
24804                    ˇif [ \"$value\" -gt 10 ]; then
24805                        ˇcontinue
24806                    ˇelif [ \"$value\" -lt 0 ]; then
24807                        ˇbreak
24808                    ˇelse
24809                        ˇecho \"$item\"
24810                    ˇfi
24811                ˇdone
24812            ˇdone
24813        ˇ}
24814    "});
24815    // test relative indent is preserved when tab
24816    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24817    cx.assert_editor_state(indoc! {"
24818        function main() {
24819                ˇfor item in $items; do
24820                    ˇwhile [ -n \"$item\" ]; do
24821                        ˇif [ \"$value\" -gt 10 ]; then
24822                            ˇcontinue
24823                        ˇelif [ \"$value\" -lt 0 ]; then
24824                            ˇbreak
24825                        ˇelse
24826                            ˇecho \"$item\"
24827                        ˇfi
24828                    ˇdone
24829                ˇdone
24830            ˇ}
24831    "});
24832
24833    // test cursor move to start of each line on tab
24834    // for `case` statement with patterns
24835    cx.set_state(indoc! {"
24836        function handle() {
24837        ˇ    case \"$1\" in
24838        ˇ        start)
24839        ˇ            echo \"a\"
24840        ˇ            ;;
24841        ˇ        stop)
24842        ˇ            echo \"b\"
24843        ˇ            ;;
24844        ˇ        *)
24845        ˇ            echo \"c\"
24846        ˇ            ;;
24847        ˇ    esac
24848        ˇ}
24849    "});
24850    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24851    cx.assert_editor_state(indoc! {"
24852        function handle() {
24853            ˇcase \"$1\" in
24854                ˇstart)
24855                    ˇecho \"a\"
24856                    ˇ;;
24857                ˇstop)
24858                    ˇecho \"b\"
24859                    ˇ;;
24860                ˇ*)
24861                    ˇecho \"c\"
24862                    ˇ;;
24863            ˇesac
24864        ˇ}
24865    "});
24866}
24867
24868#[gpui::test]
24869async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24870    init_test(cx, |_| {});
24871
24872    let mut cx = EditorTestContext::new(cx).await;
24873    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24874    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24875
24876    // test indents on comment insert
24877    cx.set_state(indoc! {"
24878        function main() {
24879        ˇ    for item in $items; do
24880        ˇ        while [ -n \"$item\" ]; do
24881        ˇ            if [ \"$value\" -gt 10 ]; then
24882        ˇ                continue
24883        ˇ            elif [ \"$value\" -lt 0 ]; then
24884        ˇ                break
24885        ˇ            else
24886        ˇ                echo \"$item\"
24887        ˇ            fi
24888        ˇ        done
24889        ˇ    done
24890        ˇ}
24891    "});
24892    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24893    cx.assert_editor_state(indoc! {"
24894        function main() {
24895        #ˇ    for item in $items; do
24896        #ˇ        while [ -n \"$item\" ]; do
24897        #ˇ            if [ \"$value\" -gt 10 ]; then
24898        #ˇ                continue
24899        #ˇ            elif [ \"$value\" -lt 0 ]; then
24900        #ˇ                break
24901        #ˇ            else
24902        #ˇ                echo \"$item\"
24903        #ˇ            fi
24904        #ˇ        done
24905        #ˇ    done
24906        #ˇ}
24907    "});
24908}
24909
24910#[gpui::test]
24911async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24912    init_test(cx, |_| {});
24913
24914    let mut cx = EditorTestContext::new(cx).await;
24915    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24916    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24917
24918    // test `else` auto outdents when typed inside `if` block
24919    cx.set_state(indoc! {"
24920        if [ \"$1\" = \"test\" ]; then
24921            echo \"foo bar\"
24922            ˇ
24923    "});
24924    cx.update_editor(|editor, window, cx| {
24925        editor.handle_input("else", window, cx);
24926    });
24927    cx.assert_editor_state(indoc! {"
24928        if [ \"$1\" = \"test\" ]; then
24929            echo \"foo bar\"
24930        elseˇ
24931    "});
24932
24933    // test `elif` auto outdents when typed inside `if` block
24934    cx.set_state(indoc! {"
24935        if [ \"$1\" = \"test\" ]; then
24936            echo \"foo bar\"
24937            ˇ
24938    "});
24939    cx.update_editor(|editor, window, cx| {
24940        editor.handle_input("elif", window, cx);
24941    });
24942    cx.assert_editor_state(indoc! {"
24943        if [ \"$1\" = \"test\" ]; then
24944            echo \"foo bar\"
24945        elifˇ
24946    "});
24947
24948    // test `fi` auto outdents when typed inside `else` block
24949    cx.set_state(indoc! {"
24950        if [ \"$1\" = \"test\" ]; then
24951            echo \"foo bar\"
24952        else
24953            echo \"bar baz\"
24954            ˇ
24955    "});
24956    cx.update_editor(|editor, window, cx| {
24957        editor.handle_input("fi", window, cx);
24958    });
24959    cx.assert_editor_state(indoc! {"
24960        if [ \"$1\" = \"test\" ]; then
24961            echo \"foo bar\"
24962        else
24963            echo \"bar baz\"
24964        fiˇ
24965    "});
24966
24967    // test `done` auto outdents when typed inside `while` block
24968    cx.set_state(indoc! {"
24969        while read line; do
24970            echo \"$line\"
24971            ˇ
24972    "});
24973    cx.update_editor(|editor, window, cx| {
24974        editor.handle_input("done", window, cx);
24975    });
24976    cx.assert_editor_state(indoc! {"
24977        while read line; do
24978            echo \"$line\"
24979        doneˇ
24980    "});
24981
24982    // test `done` auto outdents when typed inside `for` block
24983    cx.set_state(indoc! {"
24984        for file in *.txt; do
24985            cat \"$file\"
24986            ˇ
24987    "});
24988    cx.update_editor(|editor, window, cx| {
24989        editor.handle_input("done", window, cx);
24990    });
24991    cx.assert_editor_state(indoc! {"
24992        for file in *.txt; do
24993            cat \"$file\"
24994        doneˇ
24995    "});
24996
24997    // test `esac` auto outdents when typed inside `case` block
24998    cx.set_state(indoc! {"
24999        case \"$1\" in
25000            start)
25001                echo \"foo bar\"
25002                ;;
25003            stop)
25004                echo \"bar baz\"
25005                ;;
25006            ˇ
25007    "});
25008    cx.update_editor(|editor, window, cx| {
25009        editor.handle_input("esac", window, cx);
25010    });
25011    cx.assert_editor_state(indoc! {"
25012        case \"$1\" in
25013            start)
25014                echo \"foo bar\"
25015                ;;
25016            stop)
25017                echo \"bar baz\"
25018                ;;
25019        esacˇ
25020    "});
25021
25022    // test `*)` auto outdents when typed inside `case` block
25023    cx.set_state(indoc! {"
25024        case \"$1\" in
25025            start)
25026                echo \"foo bar\"
25027                ;;
25028                ˇ
25029    "});
25030    cx.update_editor(|editor, window, cx| {
25031        editor.handle_input("*)", window, cx);
25032    });
25033    cx.assert_editor_state(indoc! {"
25034        case \"$1\" in
25035            start)
25036                echo \"foo bar\"
25037                ;;
25038            *)ˇ
25039    "});
25040
25041    // test `fi` outdents to correct level with nested if blocks
25042    cx.set_state(indoc! {"
25043        if [ \"$1\" = \"test\" ]; then
25044            echo \"outer if\"
25045            if [ \"$2\" = \"debug\" ]; then
25046                echo \"inner if\"
25047                ˇ
25048    "});
25049    cx.update_editor(|editor, window, cx| {
25050        editor.handle_input("fi", window, cx);
25051    });
25052    cx.assert_editor_state(indoc! {"
25053        if [ \"$1\" = \"test\" ]; then
25054            echo \"outer if\"
25055            if [ \"$2\" = \"debug\" ]; then
25056                echo \"inner if\"
25057            fiˇ
25058    "});
25059}
25060
25061#[gpui::test]
25062async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25063    init_test(cx, |_| {});
25064    update_test_language_settings(cx, |settings| {
25065        settings.defaults.extend_comment_on_newline = Some(false);
25066    });
25067    let mut cx = EditorTestContext::new(cx).await;
25068    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25069    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25070
25071    // test correct indent after newline on comment
25072    cx.set_state(indoc! {"
25073        # COMMENT:ˇ
25074    "});
25075    cx.update_editor(|editor, window, cx| {
25076        editor.newline(&Newline, window, cx);
25077    });
25078    cx.assert_editor_state(indoc! {"
25079        # COMMENT:
25080        ˇ
25081    "});
25082
25083    // test correct indent after newline after `then`
25084    cx.set_state(indoc! {"
25085
25086        if [ \"$1\" = \"test\" ]; thenˇ
25087    "});
25088    cx.update_editor(|editor, window, cx| {
25089        editor.newline(&Newline, window, cx);
25090    });
25091    cx.run_until_parked();
25092    cx.assert_editor_state(indoc! {"
25093
25094        if [ \"$1\" = \"test\" ]; then
25095            ˇ
25096    "});
25097
25098    // test correct indent after newline after `else`
25099    cx.set_state(indoc! {"
25100        if [ \"$1\" = \"test\" ]; then
25101        elseˇ
25102    "});
25103    cx.update_editor(|editor, window, cx| {
25104        editor.newline(&Newline, window, cx);
25105    });
25106    cx.run_until_parked();
25107    cx.assert_editor_state(indoc! {"
25108        if [ \"$1\" = \"test\" ]; then
25109        else
25110            ˇ
25111    "});
25112
25113    // test correct indent after newline after `elif`
25114    cx.set_state(indoc! {"
25115        if [ \"$1\" = \"test\" ]; then
25116        elifˇ
25117    "});
25118    cx.update_editor(|editor, window, cx| {
25119        editor.newline(&Newline, window, cx);
25120    });
25121    cx.run_until_parked();
25122    cx.assert_editor_state(indoc! {"
25123        if [ \"$1\" = \"test\" ]; then
25124        elif
25125            ˇ
25126    "});
25127
25128    // test correct indent after newline after `do`
25129    cx.set_state(indoc! {"
25130        for file in *.txt; doˇ
25131    "});
25132    cx.update_editor(|editor, window, cx| {
25133        editor.newline(&Newline, window, cx);
25134    });
25135    cx.run_until_parked();
25136    cx.assert_editor_state(indoc! {"
25137        for file in *.txt; do
25138            ˇ
25139    "});
25140
25141    // test correct indent after newline after case pattern
25142    cx.set_state(indoc! {"
25143        case \"$1\" in
25144            start)ˇ
25145    "});
25146    cx.update_editor(|editor, window, cx| {
25147        editor.newline(&Newline, window, cx);
25148    });
25149    cx.run_until_parked();
25150    cx.assert_editor_state(indoc! {"
25151        case \"$1\" in
25152            start)
25153                ˇ
25154    "});
25155
25156    // test correct indent after newline after case pattern
25157    cx.set_state(indoc! {"
25158        case \"$1\" in
25159            start)
25160                ;;
25161            *)ˇ
25162    "});
25163    cx.update_editor(|editor, window, cx| {
25164        editor.newline(&Newline, window, cx);
25165    });
25166    cx.run_until_parked();
25167    cx.assert_editor_state(indoc! {"
25168        case \"$1\" in
25169            start)
25170                ;;
25171            *)
25172                ˇ
25173    "});
25174
25175    // test correct indent after newline after function opening brace
25176    cx.set_state(indoc! {"
25177        function test() {ˇ}
25178    "});
25179    cx.update_editor(|editor, window, cx| {
25180        editor.newline(&Newline, window, cx);
25181    });
25182    cx.run_until_parked();
25183    cx.assert_editor_state(indoc! {"
25184        function test() {
25185            ˇ
25186        }
25187    "});
25188
25189    // test no extra indent after semicolon on same line
25190    cx.set_state(indoc! {"
25191        echo \"test\"25192    "});
25193    cx.update_editor(|editor, window, cx| {
25194        editor.newline(&Newline, window, cx);
25195    });
25196    cx.run_until_parked();
25197    cx.assert_editor_state(indoc! {"
25198        echo \"test\";
25199        ˇ
25200    "});
25201}
25202
25203fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25204    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25205    point..point
25206}
25207
25208#[track_caller]
25209fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25210    let (text, ranges) = marked_text_ranges(marked_text, true);
25211    assert_eq!(editor.text(cx), text);
25212    assert_eq!(
25213        editor.selections.ranges(cx),
25214        ranges,
25215        "Assert selections are {}",
25216        marked_text
25217    );
25218}
25219
25220pub fn handle_signature_help_request(
25221    cx: &mut EditorLspTestContext,
25222    mocked_response: lsp::SignatureHelp,
25223) -> impl Future<Output = ()> + use<> {
25224    let mut request =
25225        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25226            let mocked_response = mocked_response.clone();
25227            async move { Ok(Some(mocked_response)) }
25228        });
25229
25230    async move {
25231        request.next().await;
25232    }
25233}
25234
25235#[track_caller]
25236pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25237    cx.update_editor(|editor, _, _| {
25238        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25239            let entries = menu.entries.borrow();
25240            let entries = entries
25241                .iter()
25242                .map(|entry| entry.string.as_str())
25243                .collect::<Vec<_>>();
25244            assert_eq!(entries, expected);
25245        } else {
25246            panic!("Expected completions menu");
25247        }
25248    });
25249}
25250
25251/// Handle completion request passing a marked string specifying where the completion
25252/// should be triggered from using '|' character, what range should be replaced, and what completions
25253/// should be returned using '<' and '>' to delimit the range.
25254///
25255/// Also see `handle_completion_request_with_insert_and_replace`.
25256#[track_caller]
25257pub fn handle_completion_request(
25258    marked_string: &str,
25259    completions: Vec<&'static str>,
25260    is_incomplete: bool,
25261    counter: Arc<AtomicUsize>,
25262    cx: &mut EditorLspTestContext,
25263) -> impl Future<Output = ()> {
25264    let complete_from_marker: TextRangeMarker = '|'.into();
25265    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25266    let (_, mut marked_ranges) = marked_text_ranges_by(
25267        marked_string,
25268        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25269    );
25270
25271    let complete_from_position =
25272        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25273    let replace_range =
25274        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25275
25276    let mut request =
25277        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25278            let completions = completions.clone();
25279            counter.fetch_add(1, atomic::Ordering::Release);
25280            async move {
25281                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25282                assert_eq!(
25283                    params.text_document_position.position,
25284                    complete_from_position
25285                );
25286                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25287                    is_incomplete,
25288                    item_defaults: None,
25289                    items: completions
25290                        .iter()
25291                        .map(|completion_text| lsp::CompletionItem {
25292                            label: completion_text.to_string(),
25293                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25294                                range: replace_range,
25295                                new_text: completion_text.to_string(),
25296                            })),
25297                            ..Default::default()
25298                        })
25299                        .collect(),
25300                })))
25301            }
25302        });
25303
25304    async move {
25305        request.next().await;
25306    }
25307}
25308
25309/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25310/// given instead, which also contains an `insert` range.
25311///
25312/// This function uses markers to define ranges:
25313/// - `|` marks the cursor position
25314/// - `<>` marks the replace range
25315/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25316pub fn handle_completion_request_with_insert_and_replace(
25317    cx: &mut EditorLspTestContext,
25318    marked_string: &str,
25319    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25320    counter: Arc<AtomicUsize>,
25321) -> impl Future<Output = ()> {
25322    let complete_from_marker: TextRangeMarker = '|'.into();
25323    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25324    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25325
25326    let (_, mut marked_ranges) = marked_text_ranges_by(
25327        marked_string,
25328        vec![
25329            complete_from_marker.clone(),
25330            replace_range_marker.clone(),
25331            insert_range_marker.clone(),
25332        ],
25333    );
25334
25335    let complete_from_position =
25336        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25337    let replace_range =
25338        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25339
25340    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25341        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25342        _ => lsp::Range {
25343            start: replace_range.start,
25344            end: complete_from_position,
25345        },
25346    };
25347
25348    let mut request =
25349        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25350            let completions = completions.clone();
25351            counter.fetch_add(1, atomic::Ordering::Release);
25352            async move {
25353                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25354                assert_eq!(
25355                    params.text_document_position.position, complete_from_position,
25356                    "marker `|` position doesn't match",
25357                );
25358                Ok(Some(lsp::CompletionResponse::Array(
25359                    completions
25360                        .iter()
25361                        .map(|(label, new_text)| lsp::CompletionItem {
25362                            label: label.to_string(),
25363                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25364                                lsp::InsertReplaceEdit {
25365                                    insert: insert_range,
25366                                    replace: replace_range,
25367                                    new_text: new_text.to_string(),
25368                                },
25369                            )),
25370                            ..Default::default()
25371                        })
25372                        .collect(),
25373                )))
25374            }
25375        });
25376
25377    async move {
25378        request.next().await;
25379    }
25380}
25381
25382fn handle_resolve_completion_request(
25383    cx: &mut EditorLspTestContext,
25384    edits: Option<Vec<(&'static str, &'static str)>>,
25385) -> impl Future<Output = ()> {
25386    let edits = edits.map(|edits| {
25387        edits
25388            .iter()
25389            .map(|(marked_string, new_text)| {
25390                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25391                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25392                lsp::TextEdit::new(replace_range, new_text.to_string())
25393            })
25394            .collect::<Vec<_>>()
25395    });
25396
25397    let mut request =
25398        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25399            let edits = edits.clone();
25400            async move {
25401                Ok(lsp::CompletionItem {
25402                    additional_text_edits: edits,
25403                    ..Default::default()
25404                })
25405            }
25406        });
25407
25408    async move {
25409        request.next().await;
25410    }
25411}
25412
25413pub(crate) fn update_test_language_settings(
25414    cx: &mut TestAppContext,
25415    f: impl Fn(&mut AllLanguageSettingsContent),
25416) {
25417    cx.update(|cx| {
25418        SettingsStore::update_global(cx, |store, cx| {
25419            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25420        });
25421    });
25422}
25423
25424pub(crate) fn update_test_project_settings(
25425    cx: &mut TestAppContext,
25426    f: impl Fn(&mut ProjectSettingsContent),
25427) {
25428    cx.update(|cx| {
25429        SettingsStore::update_global(cx, |store, cx| {
25430            store.update_user_settings(cx, |settings| f(&mut settings.project));
25431        });
25432    });
25433}
25434
25435pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25436    cx.update(|cx| {
25437        assets::Assets.load_test_fonts(cx);
25438        let store = SettingsStore::test(cx);
25439        cx.set_global(store);
25440        theme::init(theme::LoadThemes::JustBase, cx);
25441        release_channel::init(SemanticVersion::default(), cx);
25442        client::init_settings(cx);
25443        language::init(cx);
25444        Project::init_settings(cx);
25445        workspace::init_settings(cx);
25446        crate::init(cx);
25447    });
25448    zlog::init_test();
25449    update_test_language_settings(cx, f);
25450}
25451
25452#[track_caller]
25453fn assert_hunk_revert(
25454    not_reverted_text_with_selections: &str,
25455    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25456    expected_reverted_text_with_selections: &str,
25457    base_text: &str,
25458    cx: &mut EditorLspTestContext,
25459) {
25460    cx.set_state(not_reverted_text_with_selections);
25461    cx.set_head_text(base_text);
25462    cx.executor().run_until_parked();
25463
25464    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25465        let snapshot = editor.snapshot(window, cx);
25466        let reverted_hunk_statuses = snapshot
25467            .buffer_snapshot()
25468            .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
25469            .map(|hunk| hunk.status().kind)
25470            .collect::<Vec<_>>();
25471
25472        editor.git_restore(&Default::default(), window, cx);
25473        reverted_hunk_statuses
25474    });
25475    cx.executor().run_until_parked();
25476    cx.assert_editor_state(expected_reverted_text_with_selections);
25477    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25478}
25479
25480#[gpui::test(iterations = 10)]
25481async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25482    init_test(cx, |_| {});
25483
25484    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25485    let counter = diagnostic_requests.clone();
25486
25487    let fs = FakeFs::new(cx.executor());
25488    fs.insert_tree(
25489        path!("/a"),
25490        json!({
25491            "first.rs": "fn main() { let a = 5; }",
25492            "second.rs": "// Test file",
25493        }),
25494    )
25495    .await;
25496
25497    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25498    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25499    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25500
25501    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25502    language_registry.add(rust_lang());
25503    let mut fake_servers = language_registry.register_fake_lsp(
25504        "Rust",
25505        FakeLspAdapter {
25506            capabilities: lsp::ServerCapabilities {
25507                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25508                    lsp::DiagnosticOptions {
25509                        identifier: None,
25510                        inter_file_dependencies: true,
25511                        workspace_diagnostics: true,
25512                        work_done_progress_options: Default::default(),
25513                    },
25514                )),
25515                ..Default::default()
25516            },
25517            ..Default::default()
25518        },
25519    );
25520
25521    let editor = workspace
25522        .update(cx, |workspace, window, cx| {
25523            workspace.open_abs_path(
25524                PathBuf::from(path!("/a/first.rs")),
25525                OpenOptions::default(),
25526                window,
25527                cx,
25528            )
25529        })
25530        .unwrap()
25531        .await
25532        .unwrap()
25533        .downcast::<Editor>()
25534        .unwrap();
25535    let fake_server = fake_servers.next().await.unwrap();
25536    let server_id = fake_server.server.server_id();
25537    let mut first_request = fake_server
25538        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25539            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25540            let result_id = Some(new_result_id.to_string());
25541            assert_eq!(
25542                params.text_document.uri,
25543                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25544            );
25545            async move {
25546                Ok(lsp::DocumentDiagnosticReportResult::Report(
25547                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25548                        related_documents: None,
25549                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25550                            items: Vec::new(),
25551                            result_id,
25552                        },
25553                    }),
25554                ))
25555            }
25556        });
25557
25558    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25559        project.update(cx, |project, cx| {
25560            let buffer_id = editor
25561                .read(cx)
25562                .buffer()
25563                .read(cx)
25564                .as_singleton()
25565                .expect("created a singleton buffer")
25566                .read(cx)
25567                .remote_id();
25568            let buffer_result_id = project
25569                .lsp_store()
25570                .read(cx)
25571                .result_id(server_id, buffer_id, cx);
25572            assert_eq!(expected, buffer_result_id);
25573        });
25574    };
25575
25576    ensure_result_id(None, cx);
25577    cx.executor().advance_clock(Duration::from_millis(60));
25578    cx.executor().run_until_parked();
25579    assert_eq!(
25580        diagnostic_requests.load(atomic::Ordering::Acquire),
25581        1,
25582        "Opening file should trigger diagnostic request"
25583    );
25584    first_request
25585        .next()
25586        .await
25587        .expect("should have sent the first diagnostics pull request");
25588    ensure_result_id(Some("1".to_string()), cx);
25589
25590    // Editing should trigger diagnostics
25591    editor.update_in(cx, |editor, window, cx| {
25592        editor.handle_input("2", window, cx)
25593    });
25594    cx.executor().advance_clock(Duration::from_millis(60));
25595    cx.executor().run_until_parked();
25596    assert_eq!(
25597        diagnostic_requests.load(atomic::Ordering::Acquire),
25598        2,
25599        "Editing should trigger diagnostic request"
25600    );
25601    ensure_result_id(Some("2".to_string()), cx);
25602
25603    // Moving cursor should not trigger diagnostic request
25604    editor.update_in(cx, |editor, window, cx| {
25605        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25606            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25607        });
25608    });
25609    cx.executor().advance_clock(Duration::from_millis(60));
25610    cx.executor().run_until_parked();
25611    assert_eq!(
25612        diagnostic_requests.load(atomic::Ordering::Acquire),
25613        2,
25614        "Cursor movement should not trigger diagnostic request"
25615    );
25616    ensure_result_id(Some("2".to_string()), cx);
25617    // Multiple rapid edits should be debounced
25618    for _ in 0..5 {
25619        editor.update_in(cx, |editor, window, cx| {
25620            editor.handle_input("x", window, cx)
25621        });
25622    }
25623    cx.executor().advance_clock(Duration::from_millis(60));
25624    cx.executor().run_until_parked();
25625
25626    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25627    assert!(
25628        final_requests <= 4,
25629        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25630    );
25631    ensure_result_id(Some(final_requests.to_string()), cx);
25632}
25633
25634#[gpui::test]
25635async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25636    // Regression test for issue #11671
25637    // Previously, adding a cursor after moving multiple cursors would reset
25638    // the cursor count instead of adding to the existing cursors.
25639    init_test(cx, |_| {});
25640    let mut cx = EditorTestContext::new(cx).await;
25641
25642    // Create a simple buffer with cursor at start
25643    cx.set_state(indoc! {"
25644        ˇaaaa
25645        bbbb
25646        cccc
25647        dddd
25648        eeee
25649        ffff
25650        gggg
25651        hhhh"});
25652
25653    // Add 2 cursors below (so we have 3 total)
25654    cx.update_editor(|editor, window, cx| {
25655        editor.add_selection_below(&Default::default(), window, cx);
25656        editor.add_selection_below(&Default::default(), window, cx);
25657    });
25658
25659    // Verify we have 3 cursors
25660    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25661    assert_eq!(
25662        initial_count, 3,
25663        "Should have 3 cursors after adding 2 below"
25664    );
25665
25666    // Move down one line
25667    cx.update_editor(|editor, window, cx| {
25668        editor.move_down(&MoveDown, window, cx);
25669    });
25670
25671    // Add another cursor below
25672    cx.update_editor(|editor, window, cx| {
25673        editor.add_selection_below(&Default::default(), window, cx);
25674    });
25675
25676    // Should now have 4 cursors (3 original + 1 new)
25677    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25678    assert_eq!(
25679        final_count, 4,
25680        "Should have 4 cursors after moving and adding another"
25681    );
25682}
25683
25684#[gpui::test]
25685async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
25686    init_test(cx, |_| {});
25687
25688    let mut cx = EditorTestContext::new(cx).await;
25689
25690    cx.set_state(indoc!(
25691        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
25692           Second line here"#
25693    ));
25694
25695    cx.update_editor(|editor, window, cx| {
25696        // Enable soft wrapping with a narrow width to force soft wrapping and
25697        // confirm that more than 2 rows are being displayed.
25698        editor.set_wrap_width(Some(100.0.into()), cx);
25699        assert!(editor.display_text(cx).lines().count() > 2);
25700
25701        editor.add_selection_below(
25702            &AddSelectionBelow {
25703                skip_soft_wrap: true,
25704            },
25705            window,
25706            cx,
25707        );
25708
25709        assert_eq!(
25710            editor.selections.display_ranges(cx),
25711            &[
25712                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
25713                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
25714            ]
25715        );
25716
25717        editor.add_selection_above(
25718            &AddSelectionAbove {
25719                skip_soft_wrap: true,
25720            },
25721            window,
25722            cx,
25723        );
25724
25725        assert_eq!(
25726            editor.selections.display_ranges(cx),
25727            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
25728        );
25729
25730        editor.add_selection_below(
25731            &AddSelectionBelow {
25732                skip_soft_wrap: false,
25733            },
25734            window,
25735            cx,
25736        );
25737
25738        assert_eq!(
25739            editor.selections.display_ranges(cx),
25740            &[
25741                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
25742                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
25743            ]
25744        );
25745
25746        editor.add_selection_above(
25747            &AddSelectionAbove {
25748                skip_soft_wrap: false,
25749            },
25750            window,
25751            cx,
25752        );
25753
25754        assert_eq!(
25755            editor.selections.display_ranges(cx),
25756            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
25757        );
25758    });
25759}
25760
25761#[gpui::test(iterations = 10)]
25762async fn test_document_colors(cx: &mut TestAppContext) {
25763    let expected_color = Rgba {
25764        r: 0.33,
25765        g: 0.33,
25766        b: 0.33,
25767        a: 0.33,
25768    };
25769
25770    init_test(cx, |_| {});
25771
25772    let fs = FakeFs::new(cx.executor());
25773    fs.insert_tree(
25774        path!("/a"),
25775        json!({
25776            "first.rs": "fn main() { let a = 5; }",
25777        }),
25778    )
25779    .await;
25780
25781    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25782    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25783    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25784
25785    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25786    language_registry.add(rust_lang());
25787    let mut fake_servers = language_registry.register_fake_lsp(
25788        "Rust",
25789        FakeLspAdapter {
25790            capabilities: lsp::ServerCapabilities {
25791                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25792                ..lsp::ServerCapabilities::default()
25793            },
25794            name: "rust-analyzer",
25795            ..FakeLspAdapter::default()
25796        },
25797    );
25798    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25799        "Rust",
25800        FakeLspAdapter {
25801            capabilities: lsp::ServerCapabilities {
25802                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25803                ..lsp::ServerCapabilities::default()
25804            },
25805            name: "not-rust-analyzer",
25806            ..FakeLspAdapter::default()
25807        },
25808    );
25809
25810    let editor = workspace
25811        .update(cx, |workspace, window, cx| {
25812            workspace.open_abs_path(
25813                PathBuf::from(path!("/a/first.rs")),
25814                OpenOptions::default(),
25815                window,
25816                cx,
25817            )
25818        })
25819        .unwrap()
25820        .await
25821        .unwrap()
25822        .downcast::<Editor>()
25823        .unwrap();
25824    let fake_language_server = fake_servers.next().await.unwrap();
25825    let fake_language_server_without_capabilities =
25826        fake_servers_without_capabilities.next().await.unwrap();
25827    let requests_made = Arc::new(AtomicUsize::new(0));
25828    let closure_requests_made = Arc::clone(&requests_made);
25829    let mut color_request_handle = fake_language_server
25830        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25831            let requests_made = Arc::clone(&closure_requests_made);
25832            async move {
25833                assert_eq!(
25834                    params.text_document.uri,
25835                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25836                );
25837                requests_made.fetch_add(1, atomic::Ordering::Release);
25838                Ok(vec![
25839                    lsp::ColorInformation {
25840                        range: lsp::Range {
25841                            start: lsp::Position {
25842                                line: 0,
25843                                character: 0,
25844                            },
25845                            end: lsp::Position {
25846                                line: 0,
25847                                character: 1,
25848                            },
25849                        },
25850                        color: lsp::Color {
25851                            red: 0.33,
25852                            green: 0.33,
25853                            blue: 0.33,
25854                            alpha: 0.33,
25855                        },
25856                    },
25857                    lsp::ColorInformation {
25858                        range: lsp::Range {
25859                            start: lsp::Position {
25860                                line: 0,
25861                                character: 0,
25862                            },
25863                            end: lsp::Position {
25864                                line: 0,
25865                                character: 1,
25866                            },
25867                        },
25868                        color: lsp::Color {
25869                            red: 0.33,
25870                            green: 0.33,
25871                            blue: 0.33,
25872                            alpha: 0.33,
25873                        },
25874                    },
25875                ])
25876            }
25877        });
25878
25879    let _handle = fake_language_server_without_capabilities
25880        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25881            panic!("Should not be called");
25882        });
25883    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
25884    color_request_handle.next().await.unwrap();
25885    cx.run_until_parked();
25886    assert_eq!(
25887        1,
25888        requests_made.load(atomic::Ordering::Acquire),
25889        "Should query for colors once per editor open"
25890    );
25891    editor.update_in(cx, |editor, _, cx| {
25892        assert_eq!(
25893            vec![expected_color],
25894            extract_color_inlays(editor, cx),
25895            "Should have an initial inlay"
25896        );
25897    });
25898
25899    // opening another file in a split should not influence the LSP query counter
25900    workspace
25901        .update(cx, |workspace, window, cx| {
25902            assert_eq!(
25903                workspace.panes().len(),
25904                1,
25905                "Should have one pane with one editor"
25906            );
25907            workspace.move_item_to_pane_in_direction(
25908                &MoveItemToPaneInDirection {
25909                    direction: SplitDirection::Right,
25910                    focus: false,
25911                    clone: true,
25912                },
25913                window,
25914                cx,
25915            );
25916        })
25917        .unwrap();
25918    cx.run_until_parked();
25919    workspace
25920        .update(cx, |workspace, _, cx| {
25921            let panes = workspace.panes();
25922            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25923            for pane in panes {
25924                let editor = pane
25925                    .read(cx)
25926                    .active_item()
25927                    .and_then(|item| item.downcast::<Editor>())
25928                    .expect("Should have opened an editor in each split");
25929                let editor_file = editor
25930                    .read(cx)
25931                    .buffer()
25932                    .read(cx)
25933                    .as_singleton()
25934                    .expect("test deals with singleton buffers")
25935                    .read(cx)
25936                    .file()
25937                    .expect("test buffese should have a file")
25938                    .path();
25939                assert_eq!(
25940                    editor_file.as_ref(),
25941                    rel_path("first.rs"),
25942                    "Both editors should be opened for the same file"
25943                )
25944            }
25945        })
25946        .unwrap();
25947
25948    cx.executor().advance_clock(Duration::from_millis(500));
25949    let save = editor.update_in(cx, |editor, window, cx| {
25950        editor.move_to_end(&MoveToEnd, window, cx);
25951        editor.handle_input("dirty", window, cx);
25952        editor.save(
25953            SaveOptions {
25954                format: true,
25955                autosave: true,
25956            },
25957            project.clone(),
25958            window,
25959            cx,
25960        )
25961    });
25962    save.await.unwrap();
25963
25964    color_request_handle.next().await.unwrap();
25965    cx.run_until_parked();
25966    assert_eq!(
25967        2,
25968        requests_made.load(atomic::Ordering::Acquire),
25969        "Should query for colors once per save (deduplicated) and once per formatting after save"
25970    );
25971
25972    drop(editor);
25973    let close = workspace
25974        .update(cx, |workspace, window, cx| {
25975            workspace.active_pane().update(cx, |pane, cx| {
25976                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25977            })
25978        })
25979        .unwrap();
25980    close.await.unwrap();
25981    let close = workspace
25982        .update(cx, |workspace, window, cx| {
25983            workspace.active_pane().update(cx, |pane, cx| {
25984                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25985            })
25986        })
25987        .unwrap();
25988    close.await.unwrap();
25989    assert_eq!(
25990        2,
25991        requests_made.load(atomic::Ordering::Acquire),
25992        "After saving and closing all editors, no extra requests should be made"
25993    );
25994    workspace
25995        .update(cx, |workspace, _, cx| {
25996            assert!(
25997                workspace.active_item(cx).is_none(),
25998                "Should close all editors"
25999            )
26000        })
26001        .unwrap();
26002
26003    workspace
26004        .update(cx, |workspace, window, cx| {
26005            workspace.active_pane().update(cx, |pane, cx| {
26006                pane.navigate_backward(&workspace::GoBack, window, cx);
26007            })
26008        })
26009        .unwrap();
26010    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26011    cx.run_until_parked();
26012    let editor = workspace
26013        .update(cx, |workspace, _, cx| {
26014            workspace
26015                .active_item(cx)
26016                .expect("Should have reopened the editor again after navigating back")
26017                .downcast::<Editor>()
26018                .expect("Should be an editor")
26019        })
26020        .unwrap();
26021
26022    assert_eq!(
26023        2,
26024        requests_made.load(atomic::Ordering::Acquire),
26025        "Cache should be reused on buffer close and reopen"
26026    );
26027    editor.update(cx, |editor, cx| {
26028        assert_eq!(
26029            vec![expected_color],
26030            extract_color_inlays(editor, cx),
26031            "Should have an initial inlay"
26032        );
26033    });
26034
26035    drop(color_request_handle);
26036    let closure_requests_made = Arc::clone(&requests_made);
26037    let mut empty_color_request_handle = fake_language_server
26038        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26039            let requests_made = Arc::clone(&closure_requests_made);
26040            async move {
26041                assert_eq!(
26042                    params.text_document.uri,
26043                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26044                );
26045                requests_made.fetch_add(1, atomic::Ordering::Release);
26046                Ok(Vec::new())
26047            }
26048        });
26049    let save = editor.update_in(cx, |editor, window, cx| {
26050        editor.move_to_end(&MoveToEnd, window, cx);
26051        editor.handle_input("dirty_again", window, cx);
26052        editor.save(
26053            SaveOptions {
26054                format: false,
26055                autosave: true,
26056            },
26057            project.clone(),
26058            window,
26059            cx,
26060        )
26061    });
26062    save.await.unwrap();
26063
26064    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26065    empty_color_request_handle.next().await.unwrap();
26066    cx.run_until_parked();
26067    assert_eq!(
26068        3,
26069        requests_made.load(atomic::Ordering::Acquire),
26070        "Should query for colors once per save only, as formatting was not requested"
26071    );
26072    editor.update(cx, |editor, cx| {
26073        assert_eq!(
26074            Vec::<Rgba>::new(),
26075            extract_color_inlays(editor, cx),
26076            "Should clear all colors when the server returns an empty response"
26077        );
26078    });
26079}
26080
26081#[gpui::test]
26082async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
26083    init_test(cx, |_| {});
26084    let (editor, cx) = cx.add_window_view(Editor::single_line);
26085    editor.update_in(cx, |editor, window, cx| {
26086        editor.set_text("oops\n\nwow\n", window, cx)
26087    });
26088    cx.run_until_parked();
26089    editor.update(cx, |editor, cx| {
26090        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
26091    });
26092    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
26093    cx.run_until_parked();
26094    editor.update(cx, |editor, cx| {
26095        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
26096    });
26097}
26098
26099#[gpui::test]
26100async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
26101    init_test(cx, |_| {});
26102
26103    cx.update(|cx| {
26104        register_project_item::<Editor>(cx);
26105    });
26106
26107    let fs = FakeFs::new(cx.executor());
26108    fs.insert_tree("/root1", json!({})).await;
26109    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
26110        .await;
26111
26112    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
26113    let (workspace, cx) =
26114        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26115
26116    let worktree_id = project.update(cx, |project, cx| {
26117        project.worktrees(cx).next().unwrap().read(cx).id()
26118    });
26119
26120    let handle = workspace
26121        .update_in(cx, |workspace, window, cx| {
26122            let project_path = (worktree_id, rel_path("one.pdf"));
26123            workspace.open_path(project_path, None, true, window, cx)
26124        })
26125        .await
26126        .unwrap();
26127
26128    assert_eq!(
26129        handle.to_any().entity_type(),
26130        TypeId::of::<InvalidBufferView>()
26131    );
26132}
26133
26134#[gpui::test]
26135async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
26136    init_test(cx, |_| {});
26137
26138    let language = Arc::new(Language::new(
26139        LanguageConfig::default(),
26140        Some(tree_sitter_rust::LANGUAGE.into()),
26141    ));
26142
26143    // Test hierarchical sibling navigation
26144    let text = r#"
26145        fn outer() {
26146            if condition {
26147                let a = 1;
26148            }
26149            let b = 2;
26150        }
26151
26152        fn another() {
26153            let c = 3;
26154        }
26155    "#;
26156
26157    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
26158    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26159    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
26160
26161    // Wait for parsing to complete
26162    editor
26163        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
26164        .await;
26165
26166    editor.update_in(cx, |editor, window, cx| {
26167        // Start by selecting "let a = 1;" inside the if block
26168        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26169            s.select_display_ranges([
26170                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
26171            ]);
26172        });
26173
26174        let initial_selection = editor.selections.display_ranges(cx);
26175        assert_eq!(initial_selection.len(), 1, "Should have one selection");
26176
26177        // Test select next sibling - should move up levels to find the next sibling
26178        // Since "let a = 1;" has no siblings in the if block, it should move up
26179        // to find "let b = 2;" which is a sibling of the if block
26180        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26181        let next_selection = editor.selections.display_ranges(cx);
26182
26183        // Should have a selection and it should be different from the initial
26184        assert_eq!(
26185            next_selection.len(),
26186            1,
26187            "Should have one selection after next"
26188        );
26189        assert_ne!(
26190            next_selection[0], initial_selection[0],
26191            "Next sibling selection should be different"
26192        );
26193
26194        // Test hierarchical navigation by going to the end of the current function
26195        // and trying to navigate to the next function
26196        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26197            s.select_display_ranges([
26198                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
26199            ]);
26200        });
26201
26202        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26203        let function_next_selection = editor.selections.display_ranges(cx);
26204
26205        // Should move to the next function
26206        assert_eq!(
26207            function_next_selection.len(),
26208            1,
26209            "Should have one selection after function next"
26210        );
26211
26212        // Test select previous sibling navigation
26213        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
26214        let prev_selection = editor.selections.display_ranges(cx);
26215
26216        // Should have a selection and it should be different
26217        assert_eq!(
26218            prev_selection.len(),
26219            1,
26220            "Should have one selection after prev"
26221        );
26222        assert_ne!(
26223            prev_selection[0], function_next_selection[0],
26224            "Previous sibling selection should be different from next"
26225        );
26226    });
26227}
26228
26229#[gpui::test]
26230async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
26231    init_test(cx, |_| {});
26232
26233    let mut cx = EditorTestContext::new(cx).await;
26234    cx.set_state(
26235        "let ˇvariable = 42;
26236let another = variable + 1;
26237let result = variable * 2;",
26238    );
26239
26240    // Set up document highlights manually (simulating LSP response)
26241    cx.update_editor(|editor, _window, cx| {
26242        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26243
26244        // Create highlights for "variable" occurrences
26245        let highlight_ranges = [
26246            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26247            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26248            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26249        ];
26250
26251        let anchor_ranges: Vec<_> = highlight_ranges
26252            .iter()
26253            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26254            .collect();
26255
26256        editor.highlight_background::<DocumentHighlightRead>(
26257            &anchor_ranges,
26258            |theme| theme.colors().editor_document_highlight_read_background,
26259            cx,
26260        );
26261    });
26262
26263    // Go to next highlight - should move to second "variable"
26264    cx.update_editor(|editor, window, cx| {
26265        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26266    });
26267    cx.assert_editor_state(
26268        "let variable = 42;
26269let another = ˇvariable + 1;
26270let result = variable * 2;",
26271    );
26272
26273    // Go to next highlight - should move to third "variable"
26274    cx.update_editor(|editor, window, cx| {
26275        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26276    });
26277    cx.assert_editor_state(
26278        "let variable = 42;
26279let another = variable + 1;
26280let result = ˇvariable * 2;",
26281    );
26282
26283    // Go to next highlight - should stay at third "variable" (no wrap-around)
26284    cx.update_editor(|editor, window, cx| {
26285        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26286    });
26287    cx.assert_editor_state(
26288        "let variable = 42;
26289let another = variable + 1;
26290let result = ˇvariable * 2;",
26291    );
26292
26293    // Now test going backwards from third position
26294    cx.update_editor(|editor, window, cx| {
26295        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26296    });
26297    cx.assert_editor_state(
26298        "let variable = 42;
26299let another = ˇvariable + 1;
26300let result = variable * 2;",
26301    );
26302
26303    // Go to previous highlight - should move to first "variable"
26304    cx.update_editor(|editor, window, cx| {
26305        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26306    });
26307    cx.assert_editor_state(
26308        "let ˇvariable = 42;
26309let another = variable + 1;
26310let result = variable * 2;",
26311    );
26312
26313    // Go to previous highlight - should stay on first "variable"
26314    cx.update_editor(|editor, window, cx| {
26315        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26316    });
26317    cx.assert_editor_state(
26318        "let ˇvariable = 42;
26319let another = variable + 1;
26320let result = variable * 2;",
26321    );
26322}
26323
26324#[gpui::test]
26325async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26326    cx: &mut gpui::TestAppContext,
26327) {
26328    init_test(cx, |_| {});
26329
26330    let url = "https://zed.dev";
26331
26332    let markdown_language = Arc::new(Language::new(
26333        LanguageConfig {
26334            name: "Markdown".into(),
26335            ..LanguageConfig::default()
26336        },
26337        None,
26338    ));
26339
26340    let mut cx = EditorTestContext::new(cx).await;
26341    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26342    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26343
26344    cx.update_editor(|editor, window, cx| {
26345        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26346        editor.paste(&Paste, window, cx);
26347    });
26348
26349    cx.assert_editor_state(&format!(
26350        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26351    ));
26352}
26353
26354#[gpui::test]
26355async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26356    cx: &mut gpui::TestAppContext,
26357) {
26358    init_test(cx, |_| {});
26359
26360    let url = "https://zed.dev";
26361
26362    let markdown_language = Arc::new(Language::new(
26363        LanguageConfig {
26364            name: "Markdown".into(),
26365            ..LanguageConfig::default()
26366        },
26367        None,
26368    ));
26369
26370    let mut cx = EditorTestContext::new(cx).await;
26371    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26372    cx.set_state(&format!(
26373        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26374    ));
26375
26376    cx.update_editor(|editor, window, cx| {
26377        editor.copy(&Copy, window, cx);
26378    });
26379
26380    cx.set_state(&format!(
26381        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26382    ));
26383
26384    cx.update_editor(|editor, window, cx| {
26385        editor.paste(&Paste, window, cx);
26386    });
26387
26388    cx.assert_editor_state(&format!(
26389        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26390    ));
26391}
26392
26393#[gpui::test]
26394async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26395    cx: &mut gpui::TestAppContext,
26396) {
26397    init_test(cx, |_| {});
26398
26399    let url = "https://zed.dev";
26400
26401    let markdown_language = Arc::new(Language::new(
26402        LanguageConfig {
26403            name: "Markdown".into(),
26404            ..LanguageConfig::default()
26405        },
26406        None,
26407    ));
26408
26409    let mut cx = EditorTestContext::new(cx).await;
26410    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26411    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26412
26413    cx.update_editor(|editor, window, cx| {
26414        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26415        editor.paste(&Paste, window, cx);
26416    });
26417
26418    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26419}
26420
26421#[gpui::test]
26422async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26423    cx: &mut gpui::TestAppContext,
26424) {
26425    init_test(cx, |_| {});
26426
26427    let text = "Awesome";
26428
26429    let markdown_language = Arc::new(Language::new(
26430        LanguageConfig {
26431            name: "Markdown".into(),
26432            ..LanguageConfig::default()
26433        },
26434        None,
26435    ));
26436
26437    let mut cx = EditorTestContext::new(cx).await;
26438    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26439    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26440
26441    cx.update_editor(|editor, window, cx| {
26442        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26443        editor.paste(&Paste, window, cx);
26444    });
26445
26446    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26447}
26448
26449#[gpui::test]
26450async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26451    cx: &mut gpui::TestAppContext,
26452) {
26453    init_test(cx, |_| {});
26454
26455    let url = "https://zed.dev";
26456
26457    let markdown_language = Arc::new(Language::new(
26458        LanguageConfig {
26459            name: "Rust".into(),
26460            ..LanguageConfig::default()
26461        },
26462        None,
26463    ));
26464
26465    let mut cx = EditorTestContext::new(cx).await;
26466    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26467    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26468
26469    cx.update_editor(|editor, window, cx| {
26470        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26471        editor.paste(&Paste, window, cx);
26472    });
26473
26474    cx.assert_editor_state(&format!(
26475        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26476    ));
26477}
26478
26479#[gpui::test]
26480async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26481    cx: &mut TestAppContext,
26482) {
26483    init_test(cx, |_| {});
26484
26485    let url = "https://zed.dev";
26486
26487    let markdown_language = Arc::new(Language::new(
26488        LanguageConfig {
26489            name: "Markdown".into(),
26490            ..LanguageConfig::default()
26491        },
26492        None,
26493    ));
26494
26495    let (editor, cx) = cx.add_window_view(|window, cx| {
26496        let multi_buffer = MultiBuffer::build_multi(
26497            [
26498                ("this will embed -> link", vec![Point::row_range(0..1)]),
26499                ("this will replace -> link", vec![Point::row_range(0..1)]),
26500            ],
26501            cx,
26502        );
26503        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26504        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26505            s.select_ranges(vec![
26506                Point::new(0, 19)..Point::new(0, 23),
26507                Point::new(1, 21)..Point::new(1, 25),
26508            ])
26509        });
26510        let first_buffer_id = multi_buffer
26511            .read(cx)
26512            .excerpt_buffer_ids()
26513            .into_iter()
26514            .next()
26515            .unwrap();
26516        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26517        first_buffer.update(cx, |buffer, cx| {
26518            buffer.set_language(Some(markdown_language.clone()), cx);
26519        });
26520
26521        editor
26522    });
26523    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26524
26525    cx.update_editor(|editor, window, cx| {
26526        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26527        editor.paste(&Paste, window, cx);
26528    });
26529
26530    cx.assert_editor_state(&format!(
26531        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26532    ));
26533}
26534
26535#[gpui::test]
26536async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
26537    init_test(cx, |_| {});
26538
26539    let fs = FakeFs::new(cx.executor());
26540    fs.insert_tree(
26541        path!("/project"),
26542        json!({
26543            "first.rs": "# First Document\nSome content here.",
26544            "second.rs": "Plain text content for second file.",
26545        }),
26546    )
26547    .await;
26548
26549    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
26550    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26551    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26552
26553    let language = rust_lang();
26554    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26555    language_registry.add(language.clone());
26556    let mut fake_servers = language_registry.register_fake_lsp(
26557        "Rust",
26558        FakeLspAdapter {
26559            ..FakeLspAdapter::default()
26560        },
26561    );
26562
26563    let buffer1 = project
26564        .update(cx, |project, cx| {
26565            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
26566        })
26567        .await
26568        .unwrap();
26569    let buffer2 = project
26570        .update(cx, |project, cx| {
26571            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
26572        })
26573        .await
26574        .unwrap();
26575
26576    let multi_buffer = cx.new(|cx| {
26577        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
26578        multi_buffer.set_excerpts_for_path(
26579            PathKey::for_buffer(&buffer1, cx),
26580            buffer1.clone(),
26581            [Point::zero()..buffer1.read(cx).max_point()],
26582            3,
26583            cx,
26584        );
26585        multi_buffer.set_excerpts_for_path(
26586            PathKey::for_buffer(&buffer2, cx),
26587            buffer2.clone(),
26588            [Point::zero()..buffer1.read(cx).max_point()],
26589            3,
26590            cx,
26591        );
26592        multi_buffer
26593    });
26594
26595    let (editor, cx) = cx.add_window_view(|window, cx| {
26596        Editor::new(
26597            EditorMode::full(),
26598            multi_buffer,
26599            Some(project.clone()),
26600            window,
26601            cx,
26602        )
26603    });
26604
26605    let fake_language_server = fake_servers.next().await.unwrap();
26606
26607    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
26608
26609    let save = editor.update_in(cx, |editor, window, cx| {
26610        assert!(editor.is_dirty(cx));
26611
26612        editor.save(
26613            SaveOptions {
26614                format: true,
26615                autosave: true,
26616            },
26617            project,
26618            window,
26619            cx,
26620        )
26621    });
26622    let (start_edit_tx, start_edit_rx) = oneshot::channel();
26623    let (done_edit_tx, done_edit_rx) = oneshot::channel();
26624    let mut done_edit_rx = Some(done_edit_rx);
26625    let mut start_edit_tx = Some(start_edit_tx);
26626
26627    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
26628        start_edit_tx.take().unwrap().send(()).unwrap();
26629        let done_edit_rx = done_edit_rx.take().unwrap();
26630        async move {
26631            done_edit_rx.await.unwrap();
26632            Ok(None)
26633        }
26634    });
26635
26636    start_edit_rx.await.unwrap();
26637    buffer2
26638        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
26639        .unwrap();
26640
26641    done_edit_tx.send(()).unwrap();
26642
26643    save.await.unwrap();
26644    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
26645}
26646
26647#[track_caller]
26648fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26649    editor
26650        .all_inlays(cx)
26651        .into_iter()
26652        .filter_map(|inlay| inlay.get_color())
26653        .map(Rgba::from)
26654        .collect()
26655}
26656
26657#[gpui::test]
26658fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
26659    init_test(cx, |_| {});
26660
26661    let editor = cx.add_window(|window, cx| {
26662        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
26663        build_editor(buffer, window, cx)
26664    });
26665
26666    editor
26667        .update(cx, |editor, window, cx| {
26668            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26669                s.select_display_ranges([
26670                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
26671                ])
26672            });
26673
26674            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
26675
26676            assert_eq!(
26677                editor.display_text(cx),
26678                "line1\nline2\nline2",
26679                "Duplicating last line upward should create duplicate above, not on same line"
26680            );
26681
26682            assert_eq!(
26683                editor.selections.display_ranges(cx),
26684                vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)],
26685                "Selection should remain on the original line"
26686            );
26687        })
26688        .unwrap();
26689}
26690
26691#[gpui::test]
26692async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
26693    init_test(cx, |_| {});
26694
26695    let mut cx = EditorTestContext::new(cx).await;
26696
26697    cx.set_state("line1\nline2ˇ");
26698
26699    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
26700
26701    let clipboard_text = cx
26702        .read_from_clipboard()
26703        .and_then(|item| item.text().as_deref().map(str::to_string));
26704
26705    assert_eq!(
26706        clipboard_text,
26707        Some("line2\n".to_string()),
26708        "Copying a line without trailing newline should include a newline"
26709    );
26710
26711    cx.set_state("line1\nˇ");
26712
26713    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
26714
26715    cx.assert_editor_state("line1\nline2\nˇ");
26716}