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    cx.run_until_parked();
12513    // Set up a buffer white some trailing whitespace and no trailing newline.
12514    cx.set_state(
12515        &[
12516            "one ",   //
12517            "twoˇ",   //
12518            "three ", //
12519            "four",   //
12520        ]
12521        .join("\n"),
12522    );
12523
12524    // Record which buffer changes have been sent to the language server
12525    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12526    cx.lsp
12527        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12528            let buffer_changes = buffer_changes.clone();
12529            move |params, _| {
12530                buffer_changes.lock().extend(
12531                    params
12532                        .content_changes
12533                        .into_iter()
12534                        .map(|e| (e.range.unwrap(), e.text)),
12535                );
12536            }
12537        });
12538
12539    // Handle formatting requests to the language server.
12540    cx.lsp
12541        .set_request_handler::<lsp::request::Formatting, _, _>({
12542            let buffer_changes = buffer_changes.clone();
12543            move |_, _| {
12544                let buffer_changes = buffer_changes.clone();
12545                // Insert blank lines between each line of the buffer.
12546                async move {
12547                    // When formatting is requested, trailing whitespace has already been stripped,
12548                    // and the trailing newline has already been added.
12549                    assert_eq!(
12550                        &buffer_changes.lock()[1..],
12551                        &[
12552                            (
12553                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12554                                "".into()
12555                            ),
12556                            (
12557                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12558                                "".into()
12559                            ),
12560                            (
12561                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12562                                "\n".into()
12563                            ),
12564                        ]
12565                    );
12566
12567                    Ok(Some(vec![
12568                        lsp::TextEdit {
12569                            range: lsp::Range::new(
12570                                lsp::Position::new(1, 0),
12571                                lsp::Position::new(1, 0),
12572                            ),
12573                            new_text: "\n".into(),
12574                        },
12575                        lsp::TextEdit {
12576                            range: lsp::Range::new(
12577                                lsp::Position::new(2, 0),
12578                                lsp::Position::new(2, 0),
12579                            ),
12580                            new_text: "\n".into(),
12581                        },
12582                    ]))
12583                }
12584            }
12585        });
12586
12587    // Submit a format request.
12588    let format = cx
12589        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12590        .unwrap();
12591
12592    cx.run_until_parked();
12593    // After formatting the buffer, the trailing whitespace is stripped,
12594    // a newline is appended, and the edits provided by the language server
12595    // have been applied.
12596    format.await.unwrap();
12597
12598    cx.assert_editor_state(
12599        &[
12600            "one",   //
12601            "",      //
12602            "twoˇ",  //
12603            "",      //
12604            "three", //
12605            "four",  //
12606            "",      //
12607        ]
12608        .join("\n"),
12609    );
12610
12611    // Undoing the formatting undoes the trailing whitespace removal, the
12612    // trailing newline, and the LSP edits.
12613    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12614    cx.assert_editor_state(
12615        &[
12616            "one ",   //
12617            "twoˇ",   //
12618            "three ", //
12619            "four",   //
12620        ]
12621        .join("\n"),
12622    );
12623}
12624
12625#[gpui::test]
12626async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12627    cx: &mut TestAppContext,
12628) {
12629    init_test(cx, |_| {});
12630
12631    cx.update(|cx| {
12632        cx.update_global::<SettingsStore, _>(|settings, cx| {
12633            settings.update_user_settings(cx, |settings| {
12634                settings.editor.auto_signature_help = Some(true);
12635            });
12636        });
12637    });
12638
12639    let mut cx = EditorLspTestContext::new_rust(
12640        lsp::ServerCapabilities {
12641            signature_help_provider: Some(lsp::SignatureHelpOptions {
12642                ..Default::default()
12643            }),
12644            ..Default::default()
12645        },
12646        cx,
12647    )
12648    .await;
12649
12650    let language = Language::new(
12651        LanguageConfig {
12652            name: "Rust".into(),
12653            brackets: BracketPairConfig {
12654                pairs: vec![
12655                    BracketPair {
12656                        start: "{".to_string(),
12657                        end: "}".to_string(),
12658                        close: true,
12659                        surround: true,
12660                        newline: true,
12661                    },
12662                    BracketPair {
12663                        start: "(".to_string(),
12664                        end: ")".to_string(),
12665                        close: true,
12666                        surround: true,
12667                        newline: true,
12668                    },
12669                    BracketPair {
12670                        start: "/*".to_string(),
12671                        end: " */".to_string(),
12672                        close: true,
12673                        surround: true,
12674                        newline: true,
12675                    },
12676                    BracketPair {
12677                        start: "[".to_string(),
12678                        end: "]".to_string(),
12679                        close: false,
12680                        surround: false,
12681                        newline: true,
12682                    },
12683                    BracketPair {
12684                        start: "\"".to_string(),
12685                        end: "\"".to_string(),
12686                        close: true,
12687                        surround: true,
12688                        newline: false,
12689                    },
12690                    BracketPair {
12691                        start: "<".to_string(),
12692                        end: ">".to_string(),
12693                        close: false,
12694                        surround: true,
12695                        newline: true,
12696                    },
12697                ],
12698                ..Default::default()
12699            },
12700            autoclose_before: "})]".to_string(),
12701            ..Default::default()
12702        },
12703        Some(tree_sitter_rust::LANGUAGE.into()),
12704    );
12705    let language = Arc::new(language);
12706
12707    cx.language_registry().add(language.clone());
12708    cx.update_buffer(|buffer, cx| {
12709        buffer.set_language(Some(language), cx);
12710    });
12711
12712    cx.set_state(
12713        &r#"
12714            fn main() {
12715                sampleˇ
12716            }
12717        "#
12718        .unindent(),
12719    );
12720
12721    cx.update_editor(|editor, window, cx| {
12722        editor.handle_input("(", window, cx);
12723    });
12724    cx.assert_editor_state(
12725        &"
12726            fn main() {
12727                sample(ˇ)
12728            }
12729        "
12730        .unindent(),
12731    );
12732
12733    let mocked_response = lsp::SignatureHelp {
12734        signatures: vec![lsp::SignatureInformation {
12735            label: "fn sample(param1: u8, param2: u8)".to_string(),
12736            documentation: None,
12737            parameters: Some(vec![
12738                lsp::ParameterInformation {
12739                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12740                    documentation: None,
12741                },
12742                lsp::ParameterInformation {
12743                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12744                    documentation: None,
12745                },
12746            ]),
12747            active_parameter: None,
12748        }],
12749        active_signature: Some(0),
12750        active_parameter: Some(0),
12751    };
12752    handle_signature_help_request(&mut cx, mocked_response).await;
12753
12754    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12755        .await;
12756
12757    cx.editor(|editor, _, _| {
12758        let signature_help_state = editor.signature_help_state.popover().cloned();
12759        let signature = signature_help_state.unwrap();
12760        assert_eq!(
12761            signature.signatures[signature.current_signature].label,
12762            "fn sample(param1: u8, param2: u8)"
12763        );
12764    });
12765}
12766
12767#[gpui::test]
12768async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12769    init_test(cx, |_| {});
12770
12771    cx.update(|cx| {
12772        cx.update_global::<SettingsStore, _>(|settings, cx| {
12773            settings.update_user_settings(cx, |settings| {
12774                settings.editor.auto_signature_help = Some(false);
12775                settings.editor.show_signature_help_after_edits = Some(false);
12776            });
12777        });
12778    });
12779
12780    let mut cx = EditorLspTestContext::new_rust(
12781        lsp::ServerCapabilities {
12782            signature_help_provider: Some(lsp::SignatureHelpOptions {
12783                ..Default::default()
12784            }),
12785            ..Default::default()
12786        },
12787        cx,
12788    )
12789    .await;
12790
12791    let language = Language::new(
12792        LanguageConfig {
12793            name: "Rust".into(),
12794            brackets: BracketPairConfig {
12795                pairs: vec![
12796                    BracketPair {
12797                        start: "{".to_string(),
12798                        end: "}".to_string(),
12799                        close: true,
12800                        surround: true,
12801                        newline: true,
12802                    },
12803                    BracketPair {
12804                        start: "(".to_string(),
12805                        end: ")".to_string(),
12806                        close: true,
12807                        surround: true,
12808                        newline: true,
12809                    },
12810                    BracketPair {
12811                        start: "/*".to_string(),
12812                        end: " */".to_string(),
12813                        close: true,
12814                        surround: true,
12815                        newline: true,
12816                    },
12817                    BracketPair {
12818                        start: "[".to_string(),
12819                        end: "]".to_string(),
12820                        close: false,
12821                        surround: false,
12822                        newline: true,
12823                    },
12824                    BracketPair {
12825                        start: "\"".to_string(),
12826                        end: "\"".to_string(),
12827                        close: true,
12828                        surround: true,
12829                        newline: false,
12830                    },
12831                    BracketPair {
12832                        start: "<".to_string(),
12833                        end: ">".to_string(),
12834                        close: false,
12835                        surround: true,
12836                        newline: true,
12837                    },
12838                ],
12839                ..Default::default()
12840            },
12841            autoclose_before: "})]".to_string(),
12842            ..Default::default()
12843        },
12844        Some(tree_sitter_rust::LANGUAGE.into()),
12845    );
12846    let language = Arc::new(language);
12847
12848    cx.language_registry().add(language.clone());
12849    cx.update_buffer(|buffer, cx| {
12850        buffer.set_language(Some(language), cx);
12851    });
12852
12853    // Ensure that signature_help is not called when no signature help is enabled.
12854    cx.set_state(
12855        &r#"
12856            fn main() {
12857                sampleˇ
12858            }
12859        "#
12860        .unindent(),
12861    );
12862    cx.update_editor(|editor, window, cx| {
12863        editor.handle_input("(", window, cx);
12864    });
12865    cx.assert_editor_state(
12866        &"
12867            fn main() {
12868                sample(ˇ)
12869            }
12870        "
12871        .unindent(),
12872    );
12873    cx.editor(|editor, _, _| {
12874        assert!(editor.signature_help_state.task().is_none());
12875    });
12876
12877    let mocked_response = lsp::SignatureHelp {
12878        signatures: vec![lsp::SignatureInformation {
12879            label: "fn sample(param1: u8, param2: u8)".to_string(),
12880            documentation: None,
12881            parameters: Some(vec![
12882                lsp::ParameterInformation {
12883                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12884                    documentation: None,
12885                },
12886                lsp::ParameterInformation {
12887                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12888                    documentation: None,
12889                },
12890            ]),
12891            active_parameter: None,
12892        }],
12893        active_signature: Some(0),
12894        active_parameter: Some(0),
12895    };
12896
12897    // Ensure that signature_help is called when enabled afte edits
12898    cx.update(|_, cx| {
12899        cx.update_global::<SettingsStore, _>(|settings, cx| {
12900            settings.update_user_settings(cx, |settings| {
12901                settings.editor.auto_signature_help = Some(false);
12902                settings.editor.show_signature_help_after_edits = Some(true);
12903            });
12904        });
12905    });
12906    cx.set_state(
12907        &r#"
12908            fn main() {
12909                sampleˇ
12910            }
12911        "#
12912        .unindent(),
12913    );
12914    cx.update_editor(|editor, window, cx| {
12915        editor.handle_input("(", window, cx);
12916    });
12917    cx.assert_editor_state(
12918        &"
12919            fn main() {
12920                sample(ˇ)
12921            }
12922        "
12923        .unindent(),
12924    );
12925    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12926    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12927        .await;
12928    cx.update_editor(|editor, _, _| {
12929        let signature_help_state = editor.signature_help_state.popover().cloned();
12930        assert!(signature_help_state.is_some());
12931        let signature = signature_help_state.unwrap();
12932        assert_eq!(
12933            signature.signatures[signature.current_signature].label,
12934            "fn sample(param1: u8, param2: u8)"
12935        );
12936        editor.signature_help_state = SignatureHelpState::default();
12937    });
12938
12939    // Ensure that signature_help is called when auto signature help override is enabled
12940    cx.update(|_, cx| {
12941        cx.update_global::<SettingsStore, _>(|settings, cx| {
12942            settings.update_user_settings(cx, |settings| {
12943                settings.editor.auto_signature_help = Some(true);
12944                settings.editor.show_signature_help_after_edits = Some(false);
12945            });
12946        });
12947    });
12948    cx.set_state(
12949        &r#"
12950            fn main() {
12951                sampleˇ
12952            }
12953        "#
12954        .unindent(),
12955    );
12956    cx.update_editor(|editor, window, cx| {
12957        editor.handle_input("(", window, cx);
12958    });
12959    cx.assert_editor_state(
12960        &"
12961            fn main() {
12962                sample(ˇ)
12963            }
12964        "
12965        .unindent(),
12966    );
12967    handle_signature_help_request(&mut cx, mocked_response).await;
12968    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12969        .await;
12970    cx.editor(|editor, _, _| {
12971        let signature_help_state = editor.signature_help_state.popover().cloned();
12972        assert!(signature_help_state.is_some());
12973        let signature = signature_help_state.unwrap();
12974        assert_eq!(
12975            signature.signatures[signature.current_signature].label,
12976            "fn sample(param1: u8, param2: u8)"
12977        );
12978    });
12979}
12980
12981#[gpui::test]
12982async fn test_signature_help(cx: &mut TestAppContext) {
12983    init_test(cx, |_| {});
12984    cx.update(|cx| {
12985        cx.update_global::<SettingsStore, _>(|settings, cx| {
12986            settings.update_user_settings(cx, |settings| {
12987                settings.editor.auto_signature_help = Some(true);
12988            });
12989        });
12990    });
12991
12992    let mut cx = EditorLspTestContext::new_rust(
12993        lsp::ServerCapabilities {
12994            signature_help_provider: Some(lsp::SignatureHelpOptions {
12995                ..Default::default()
12996            }),
12997            ..Default::default()
12998        },
12999        cx,
13000    )
13001    .await;
13002
13003    // A test that directly calls `show_signature_help`
13004    cx.update_editor(|editor, window, cx| {
13005        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13006    });
13007
13008    let mocked_response = lsp::SignatureHelp {
13009        signatures: vec![lsp::SignatureInformation {
13010            label: "fn sample(param1: u8, param2: u8)".to_string(),
13011            documentation: None,
13012            parameters: Some(vec![
13013                lsp::ParameterInformation {
13014                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13015                    documentation: None,
13016                },
13017                lsp::ParameterInformation {
13018                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13019                    documentation: None,
13020                },
13021            ]),
13022            active_parameter: None,
13023        }],
13024        active_signature: Some(0),
13025        active_parameter: Some(0),
13026    };
13027    handle_signature_help_request(&mut cx, mocked_response).await;
13028
13029    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13030        .await;
13031
13032    cx.editor(|editor, _, _| {
13033        let signature_help_state = editor.signature_help_state.popover().cloned();
13034        assert!(signature_help_state.is_some());
13035        let signature = signature_help_state.unwrap();
13036        assert_eq!(
13037            signature.signatures[signature.current_signature].label,
13038            "fn sample(param1: u8, param2: u8)"
13039        );
13040    });
13041
13042    // When exiting outside from inside the brackets, `signature_help` is closed.
13043    cx.set_state(indoc! {"
13044        fn main() {
13045            sample(ˇ);
13046        }
13047
13048        fn sample(param1: u8, param2: u8) {}
13049    "});
13050
13051    cx.update_editor(|editor, window, cx| {
13052        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13053            s.select_ranges([0..0])
13054        });
13055    });
13056
13057    let mocked_response = lsp::SignatureHelp {
13058        signatures: Vec::new(),
13059        active_signature: None,
13060        active_parameter: None,
13061    };
13062    handle_signature_help_request(&mut cx, mocked_response).await;
13063
13064    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13065        .await;
13066
13067    cx.editor(|editor, _, _| {
13068        assert!(!editor.signature_help_state.is_shown());
13069    });
13070
13071    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13072    cx.set_state(indoc! {"
13073        fn main() {
13074            sample(ˇ);
13075        }
13076
13077        fn sample(param1: u8, param2: u8) {}
13078    "});
13079
13080    let mocked_response = lsp::SignatureHelp {
13081        signatures: vec![lsp::SignatureInformation {
13082            label: "fn sample(param1: u8, param2: u8)".to_string(),
13083            documentation: None,
13084            parameters: Some(vec![
13085                lsp::ParameterInformation {
13086                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13087                    documentation: None,
13088                },
13089                lsp::ParameterInformation {
13090                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13091                    documentation: None,
13092                },
13093            ]),
13094            active_parameter: None,
13095        }],
13096        active_signature: Some(0),
13097        active_parameter: Some(0),
13098    };
13099    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13100    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13101        .await;
13102    cx.editor(|editor, _, _| {
13103        assert!(editor.signature_help_state.is_shown());
13104    });
13105
13106    // Restore the popover with more parameter input
13107    cx.set_state(indoc! {"
13108        fn main() {
13109            sample(param1, param2ˇ);
13110        }
13111
13112        fn sample(param1: u8, param2: u8) {}
13113    "});
13114
13115    let mocked_response = lsp::SignatureHelp {
13116        signatures: vec![lsp::SignatureInformation {
13117            label: "fn sample(param1: u8, param2: u8)".to_string(),
13118            documentation: None,
13119            parameters: Some(vec![
13120                lsp::ParameterInformation {
13121                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13122                    documentation: None,
13123                },
13124                lsp::ParameterInformation {
13125                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13126                    documentation: None,
13127                },
13128            ]),
13129            active_parameter: None,
13130        }],
13131        active_signature: Some(0),
13132        active_parameter: Some(1),
13133    };
13134    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13135    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13136        .await;
13137
13138    // When selecting a range, the popover is gone.
13139    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13140    cx.update_editor(|editor, window, cx| {
13141        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13142            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13143        })
13144    });
13145    cx.assert_editor_state(indoc! {"
13146        fn main() {
13147            sample(param1, «ˇparam2»);
13148        }
13149
13150        fn sample(param1: u8, param2: u8) {}
13151    "});
13152    cx.editor(|editor, _, _| {
13153        assert!(!editor.signature_help_state.is_shown());
13154    });
13155
13156    // When unselecting again, the popover is back if within the brackets.
13157    cx.update_editor(|editor, window, cx| {
13158        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13159            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13160        })
13161    });
13162    cx.assert_editor_state(indoc! {"
13163        fn main() {
13164            sample(param1, ˇparam2);
13165        }
13166
13167        fn sample(param1: u8, param2: u8) {}
13168    "});
13169    handle_signature_help_request(&mut cx, mocked_response).await;
13170    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13171        .await;
13172    cx.editor(|editor, _, _| {
13173        assert!(editor.signature_help_state.is_shown());
13174    });
13175
13176    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13177    cx.update_editor(|editor, window, cx| {
13178        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13179            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13180            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13181        })
13182    });
13183    cx.assert_editor_state(indoc! {"
13184        fn main() {
13185            sample(param1, ˇparam2);
13186        }
13187
13188        fn sample(param1: u8, param2: u8) {}
13189    "});
13190
13191    let mocked_response = lsp::SignatureHelp {
13192        signatures: vec![lsp::SignatureInformation {
13193            label: "fn sample(param1: u8, param2: u8)".to_string(),
13194            documentation: None,
13195            parameters: Some(vec![
13196                lsp::ParameterInformation {
13197                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13198                    documentation: None,
13199                },
13200                lsp::ParameterInformation {
13201                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13202                    documentation: None,
13203                },
13204            ]),
13205            active_parameter: None,
13206        }],
13207        active_signature: Some(0),
13208        active_parameter: Some(1),
13209    };
13210    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13211    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13212        .await;
13213    cx.update_editor(|editor, _, cx| {
13214        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13215    });
13216    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13217        .await;
13218    cx.update_editor(|editor, window, cx| {
13219        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13220            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13221        })
13222    });
13223    cx.assert_editor_state(indoc! {"
13224        fn main() {
13225            sample(param1, «ˇparam2»);
13226        }
13227
13228        fn sample(param1: u8, param2: u8) {}
13229    "});
13230    cx.update_editor(|editor, window, cx| {
13231        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13232            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13233        })
13234    });
13235    cx.assert_editor_state(indoc! {"
13236        fn main() {
13237            sample(param1, ˇparam2);
13238        }
13239
13240        fn sample(param1: u8, param2: u8) {}
13241    "});
13242    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13243        .await;
13244}
13245
13246#[gpui::test]
13247async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13248    init_test(cx, |_| {});
13249
13250    let mut cx = EditorLspTestContext::new_rust(
13251        lsp::ServerCapabilities {
13252            signature_help_provider: Some(lsp::SignatureHelpOptions {
13253                ..Default::default()
13254            }),
13255            ..Default::default()
13256        },
13257        cx,
13258    )
13259    .await;
13260
13261    cx.set_state(indoc! {"
13262        fn main() {
13263            overloadedˇ
13264        }
13265    "});
13266
13267    cx.update_editor(|editor, window, cx| {
13268        editor.handle_input("(", window, cx);
13269        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13270    });
13271
13272    // Mock response with 3 signatures
13273    let mocked_response = lsp::SignatureHelp {
13274        signatures: vec![
13275            lsp::SignatureInformation {
13276                label: "fn overloaded(x: i32)".to_string(),
13277                documentation: None,
13278                parameters: Some(vec![lsp::ParameterInformation {
13279                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13280                    documentation: None,
13281                }]),
13282                active_parameter: None,
13283            },
13284            lsp::SignatureInformation {
13285                label: "fn overloaded(x: i32, y: i32)".to_string(),
13286                documentation: None,
13287                parameters: Some(vec![
13288                    lsp::ParameterInformation {
13289                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13290                        documentation: None,
13291                    },
13292                    lsp::ParameterInformation {
13293                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13294                        documentation: None,
13295                    },
13296                ]),
13297                active_parameter: None,
13298            },
13299            lsp::SignatureInformation {
13300                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13301                documentation: None,
13302                parameters: Some(vec![
13303                    lsp::ParameterInformation {
13304                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13305                        documentation: None,
13306                    },
13307                    lsp::ParameterInformation {
13308                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13309                        documentation: None,
13310                    },
13311                    lsp::ParameterInformation {
13312                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13313                        documentation: None,
13314                    },
13315                ]),
13316                active_parameter: None,
13317            },
13318        ],
13319        active_signature: Some(1),
13320        active_parameter: Some(0),
13321    };
13322    handle_signature_help_request(&mut cx, mocked_response).await;
13323
13324    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13325        .await;
13326
13327    // Verify we have multiple signatures and the right one is selected
13328    cx.editor(|editor, _, _| {
13329        let popover = editor.signature_help_state.popover().cloned().unwrap();
13330        assert_eq!(popover.signatures.len(), 3);
13331        // active_signature was 1, so that should be the current
13332        assert_eq!(popover.current_signature, 1);
13333        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13334        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13335        assert_eq!(
13336            popover.signatures[2].label,
13337            "fn overloaded(x: i32, y: i32, z: i32)"
13338        );
13339    });
13340
13341    // Test navigation functionality
13342    cx.update_editor(|editor, window, cx| {
13343        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13344    });
13345
13346    cx.editor(|editor, _, _| {
13347        let popover = editor.signature_help_state.popover().cloned().unwrap();
13348        assert_eq!(popover.current_signature, 2);
13349    });
13350
13351    // Test wrap around
13352    cx.update_editor(|editor, window, cx| {
13353        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13354    });
13355
13356    cx.editor(|editor, _, _| {
13357        let popover = editor.signature_help_state.popover().cloned().unwrap();
13358        assert_eq!(popover.current_signature, 0);
13359    });
13360
13361    // Test previous navigation
13362    cx.update_editor(|editor, window, cx| {
13363        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13364    });
13365
13366    cx.editor(|editor, _, _| {
13367        let popover = editor.signature_help_state.popover().cloned().unwrap();
13368        assert_eq!(popover.current_signature, 2);
13369    });
13370}
13371
13372#[gpui::test]
13373async fn test_completion_mode(cx: &mut TestAppContext) {
13374    init_test(cx, |_| {});
13375    let mut cx = EditorLspTestContext::new_rust(
13376        lsp::ServerCapabilities {
13377            completion_provider: Some(lsp::CompletionOptions {
13378                resolve_provider: Some(true),
13379                ..Default::default()
13380            }),
13381            ..Default::default()
13382        },
13383        cx,
13384    )
13385    .await;
13386
13387    struct Run {
13388        run_description: &'static str,
13389        initial_state: String,
13390        buffer_marked_text: String,
13391        completion_label: &'static str,
13392        completion_text: &'static str,
13393        expected_with_insert_mode: String,
13394        expected_with_replace_mode: String,
13395        expected_with_replace_subsequence_mode: String,
13396        expected_with_replace_suffix_mode: String,
13397    }
13398
13399    let runs = [
13400        Run {
13401            run_description: "Start of word matches completion text",
13402            initial_state: "before ediˇ after".into(),
13403            buffer_marked_text: "before <edi|> after".into(),
13404            completion_label: "editor",
13405            completion_text: "editor",
13406            expected_with_insert_mode: "before editorˇ after".into(),
13407            expected_with_replace_mode: "before editorˇ after".into(),
13408            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13409            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13410        },
13411        Run {
13412            run_description: "Accept same text at the middle of the word",
13413            initial_state: "before ediˇtor after".into(),
13414            buffer_marked_text: "before <edi|tor> after".into(),
13415            completion_label: "editor",
13416            completion_text: "editor",
13417            expected_with_insert_mode: "before editorˇtor after".into(),
13418            expected_with_replace_mode: "before editorˇ after".into(),
13419            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13420            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13421        },
13422        Run {
13423            run_description: "End of word matches completion text -- cursor at end",
13424            initial_state: "before torˇ after".into(),
13425            buffer_marked_text: "before <tor|> after".into(),
13426            completion_label: "editor",
13427            completion_text: "editor",
13428            expected_with_insert_mode: "before editorˇ after".into(),
13429            expected_with_replace_mode: "before editorˇ after".into(),
13430            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13431            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13432        },
13433        Run {
13434            run_description: "End of word matches completion text -- cursor at start",
13435            initial_state: "before ˇtor after".into(),
13436            buffer_marked_text: "before <|tor> after".into(),
13437            completion_label: "editor",
13438            completion_text: "editor",
13439            expected_with_insert_mode: "before editorˇtor after".into(),
13440            expected_with_replace_mode: "before editorˇ after".into(),
13441            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13442            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13443        },
13444        Run {
13445            run_description: "Prepend text containing whitespace",
13446            initial_state: "pˇfield: bool".into(),
13447            buffer_marked_text: "<p|field>: bool".into(),
13448            completion_label: "pub ",
13449            completion_text: "pub ",
13450            expected_with_insert_mode: "pub ˇfield: bool".into(),
13451            expected_with_replace_mode: "pub ˇ: bool".into(),
13452            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13453            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13454        },
13455        Run {
13456            run_description: "Add element to start of list",
13457            initial_state: "[element_ˇelement_2]".into(),
13458            buffer_marked_text: "[<element_|element_2>]".into(),
13459            completion_label: "element_1",
13460            completion_text: "element_1",
13461            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13462            expected_with_replace_mode: "[element_1ˇ]".into(),
13463            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13464            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13465        },
13466        Run {
13467            run_description: "Add element to start of list -- first and second elements are equal",
13468            initial_state: "[elˇelement]".into(),
13469            buffer_marked_text: "[<el|element>]".into(),
13470            completion_label: "element",
13471            completion_text: "element",
13472            expected_with_insert_mode: "[elementˇelement]".into(),
13473            expected_with_replace_mode: "[elementˇ]".into(),
13474            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13475            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13476        },
13477        Run {
13478            run_description: "Ends with matching suffix",
13479            initial_state: "SubˇError".into(),
13480            buffer_marked_text: "<Sub|Error>".into(),
13481            completion_label: "SubscriptionError",
13482            completion_text: "SubscriptionError",
13483            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13484            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13485            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13486            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13487        },
13488        Run {
13489            run_description: "Suffix is a subsequence -- contiguous",
13490            initial_state: "SubˇErr".into(),
13491            buffer_marked_text: "<Sub|Err>".into(),
13492            completion_label: "SubscriptionError",
13493            completion_text: "SubscriptionError",
13494            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13495            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13496            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13497            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13498        },
13499        Run {
13500            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13501            initial_state: "Suˇscrirr".into(),
13502            buffer_marked_text: "<Su|scrirr>".into(),
13503            completion_label: "SubscriptionError",
13504            completion_text: "SubscriptionError",
13505            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13506            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13507            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13508            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13509        },
13510        Run {
13511            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13512            initial_state: "foo(indˇix)".into(),
13513            buffer_marked_text: "foo(<ind|ix>)".into(),
13514            completion_label: "node_index",
13515            completion_text: "node_index",
13516            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13517            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13518            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13519            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13520        },
13521        Run {
13522            run_description: "Replace range ends before cursor - should extend to cursor",
13523            initial_state: "before editˇo after".into(),
13524            buffer_marked_text: "before <{ed}>it|o after".into(),
13525            completion_label: "editor",
13526            completion_text: "editor",
13527            expected_with_insert_mode: "before editorˇo after".into(),
13528            expected_with_replace_mode: "before editorˇo after".into(),
13529            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13530            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13531        },
13532        Run {
13533            run_description: "Uses label for suffix matching",
13534            initial_state: "before ediˇtor after".into(),
13535            buffer_marked_text: "before <edi|tor> after".into(),
13536            completion_label: "editor",
13537            completion_text: "editor()",
13538            expected_with_insert_mode: "before editor()ˇtor after".into(),
13539            expected_with_replace_mode: "before editor()ˇ after".into(),
13540            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13541            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13542        },
13543        Run {
13544            run_description: "Case insensitive subsequence and suffix matching",
13545            initial_state: "before EDiˇtoR after".into(),
13546            buffer_marked_text: "before <EDi|toR> after".into(),
13547            completion_label: "editor",
13548            completion_text: "editor",
13549            expected_with_insert_mode: "before editorˇtoR after".into(),
13550            expected_with_replace_mode: "before editorˇ after".into(),
13551            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13552            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13553        },
13554    ];
13555
13556    for run in runs {
13557        let run_variations = [
13558            (LspInsertMode::Insert, run.expected_with_insert_mode),
13559            (LspInsertMode::Replace, run.expected_with_replace_mode),
13560            (
13561                LspInsertMode::ReplaceSubsequence,
13562                run.expected_with_replace_subsequence_mode,
13563            ),
13564            (
13565                LspInsertMode::ReplaceSuffix,
13566                run.expected_with_replace_suffix_mode,
13567            ),
13568        ];
13569
13570        for (lsp_insert_mode, expected_text) in run_variations {
13571            eprintln!(
13572                "run = {:?}, mode = {lsp_insert_mode:.?}",
13573                run.run_description,
13574            );
13575
13576            update_test_language_settings(&mut cx, |settings| {
13577                settings.defaults.completions = Some(CompletionSettingsContent {
13578                    lsp_insert_mode: Some(lsp_insert_mode),
13579                    words: Some(WordsCompletionMode::Disabled),
13580                    words_min_length: Some(0),
13581                    ..Default::default()
13582                });
13583            });
13584
13585            cx.set_state(&run.initial_state);
13586            cx.update_editor(|editor, window, cx| {
13587                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13588            });
13589
13590            let counter = Arc::new(AtomicUsize::new(0));
13591            handle_completion_request_with_insert_and_replace(
13592                &mut cx,
13593                &run.buffer_marked_text,
13594                vec![(run.completion_label, run.completion_text)],
13595                counter.clone(),
13596            )
13597            .await;
13598            cx.condition(|editor, _| editor.context_menu_visible())
13599                .await;
13600            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13601
13602            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13603                editor
13604                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13605                    .unwrap()
13606            });
13607            cx.assert_editor_state(&expected_text);
13608            handle_resolve_completion_request(&mut cx, None).await;
13609            apply_additional_edits.await.unwrap();
13610        }
13611    }
13612}
13613
13614#[gpui::test]
13615async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13616    init_test(cx, |_| {});
13617    let mut cx = EditorLspTestContext::new_rust(
13618        lsp::ServerCapabilities {
13619            completion_provider: Some(lsp::CompletionOptions {
13620                resolve_provider: Some(true),
13621                ..Default::default()
13622            }),
13623            ..Default::default()
13624        },
13625        cx,
13626    )
13627    .await;
13628
13629    let initial_state = "SubˇError";
13630    let buffer_marked_text = "<Sub|Error>";
13631    let completion_text = "SubscriptionError";
13632    let expected_with_insert_mode = "SubscriptionErrorˇError";
13633    let expected_with_replace_mode = "SubscriptionErrorˇ";
13634
13635    update_test_language_settings(&mut cx, |settings| {
13636        settings.defaults.completions = Some(CompletionSettingsContent {
13637            words: Some(WordsCompletionMode::Disabled),
13638            words_min_length: Some(0),
13639            // set the opposite here to ensure that the action is overriding the default behavior
13640            lsp_insert_mode: Some(LspInsertMode::Insert),
13641            ..Default::default()
13642        });
13643    });
13644
13645    cx.set_state(initial_state);
13646    cx.update_editor(|editor, window, cx| {
13647        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13648    });
13649
13650    let counter = Arc::new(AtomicUsize::new(0));
13651    handle_completion_request_with_insert_and_replace(
13652        &mut cx,
13653        buffer_marked_text,
13654        vec![(completion_text, completion_text)],
13655        counter.clone(),
13656    )
13657    .await;
13658    cx.condition(|editor, _| editor.context_menu_visible())
13659        .await;
13660    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13661
13662    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13663        editor
13664            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13665            .unwrap()
13666    });
13667    cx.assert_editor_state(expected_with_replace_mode);
13668    handle_resolve_completion_request(&mut cx, None).await;
13669    apply_additional_edits.await.unwrap();
13670
13671    update_test_language_settings(&mut cx, |settings| {
13672        settings.defaults.completions = Some(CompletionSettingsContent {
13673            words: Some(WordsCompletionMode::Disabled),
13674            words_min_length: Some(0),
13675            // set the opposite here to ensure that the action is overriding the default behavior
13676            lsp_insert_mode: Some(LspInsertMode::Replace),
13677            ..Default::default()
13678        });
13679    });
13680
13681    cx.set_state(initial_state);
13682    cx.update_editor(|editor, window, cx| {
13683        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13684    });
13685    handle_completion_request_with_insert_and_replace(
13686        &mut cx,
13687        buffer_marked_text,
13688        vec![(completion_text, completion_text)],
13689        counter.clone(),
13690    )
13691    .await;
13692    cx.condition(|editor, _| editor.context_menu_visible())
13693        .await;
13694    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13695
13696    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13697        editor
13698            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13699            .unwrap()
13700    });
13701    cx.assert_editor_state(expected_with_insert_mode);
13702    handle_resolve_completion_request(&mut cx, None).await;
13703    apply_additional_edits.await.unwrap();
13704}
13705
13706#[gpui::test]
13707async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13708    init_test(cx, |_| {});
13709    let mut cx = EditorLspTestContext::new_rust(
13710        lsp::ServerCapabilities {
13711            completion_provider: Some(lsp::CompletionOptions {
13712                resolve_provider: Some(true),
13713                ..Default::default()
13714            }),
13715            ..Default::default()
13716        },
13717        cx,
13718    )
13719    .await;
13720
13721    // scenario: surrounding text matches completion text
13722    let completion_text = "to_offset";
13723    let initial_state = indoc! {"
13724        1. buf.to_offˇsuffix
13725        2. buf.to_offˇsuf
13726        3. buf.to_offˇfix
13727        4. buf.to_offˇ
13728        5. into_offˇensive
13729        6. ˇsuffix
13730        7. let ˇ //
13731        8. aaˇzz
13732        9. buf.to_off«zzzzzˇ»suffix
13733        10. buf.«ˇzzzzz»suffix
13734        11. to_off«ˇzzzzz»
13735
13736        buf.to_offˇsuffix  // newest cursor
13737    "};
13738    let completion_marked_buffer = indoc! {"
13739        1. buf.to_offsuffix
13740        2. buf.to_offsuf
13741        3. buf.to_offfix
13742        4. buf.to_off
13743        5. into_offensive
13744        6. suffix
13745        7. let  //
13746        8. aazz
13747        9. buf.to_offzzzzzsuffix
13748        10. buf.zzzzzsuffix
13749        11. to_offzzzzz
13750
13751        buf.<to_off|suffix>  // newest cursor
13752    "};
13753    let expected = indoc! {"
13754        1. buf.to_offsetˇ
13755        2. buf.to_offsetˇsuf
13756        3. buf.to_offsetˇfix
13757        4. buf.to_offsetˇ
13758        5. into_offsetˇensive
13759        6. to_offsetˇsuffix
13760        7. let to_offsetˇ //
13761        8. aato_offsetˇzz
13762        9. buf.to_offsetˇ
13763        10. buf.to_offsetˇsuffix
13764        11. to_offsetˇ
13765
13766        buf.to_offsetˇ  // newest cursor
13767    "};
13768    cx.set_state(initial_state);
13769    cx.update_editor(|editor, window, cx| {
13770        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13771    });
13772    handle_completion_request_with_insert_and_replace(
13773        &mut cx,
13774        completion_marked_buffer,
13775        vec![(completion_text, completion_text)],
13776        Arc::new(AtomicUsize::new(0)),
13777    )
13778    .await;
13779    cx.condition(|editor, _| editor.context_menu_visible())
13780        .await;
13781    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13782        editor
13783            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13784            .unwrap()
13785    });
13786    cx.assert_editor_state(expected);
13787    handle_resolve_completion_request(&mut cx, None).await;
13788    apply_additional_edits.await.unwrap();
13789
13790    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13791    let completion_text = "foo_and_bar";
13792    let initial_state = indoc! {"
13793        1. ooanbˇ
13794        2. zooanbˇ
13795        3. ooanbˇz
13796        4. zooanbˇz
13797        5. ooanˇ
13798        6. oanbˇ
13799
13800        ooanbˇ
13801    "};
13802    let completion_marked_buffer = indoc! {"
13803        1. ooanb
13804        2. zooanb
13805        3. ooanbz
13806        4. zooanbz
13807        5. ooan
13808        6. oanb
13809
13810        <ooanb|>
13811    "};
13812    let expected = indoc! {"
13813        1. foo_and_barˇ
13814        2. zfoo_and_barˇ
13815        3. foo_and_barˇz
13816        4. zfoo_and_barˇz
13817        5. ooanfoo_and_barˇ
13818        6. oanbfoo_and_barˇ
13819
13820        foo_and_barˇ
13821    "};
13822    cx.set_state(initial_state);
13823    cx.update_editor(|editor, window, cx| {
13824        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13825    });
13826    handle_completion_request_with_insert_and_replace(
13827        &mut cx,
13828        completion_marked_buffer,
13829        vec![(completion_text, completion_text)],
13830        Arc::new(AtomicUsize::new(0)),
13831    )
13832    .await;
13833    cx.condition(|editor, _| editor.context_menu_visible())
13834        .await;
13835    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13836        editor
13837            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13838            .unwrap()
13839    });
13840    cx.assert_editor_state(expected);
13841    handle_resolve_completion_request(&mut cx, None).await;
13842    apply_additional_edits.await.unwrap();
13843
13844    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13845    // (expects the same as if it was inserted at the end)
13846    let completion_text = "foo_and_bar";
13847    let initial_state = indoc! {"
13848        1. ooˇanb
13849        2. zooˇanb
13850        3. ooˇanbz
13851        4. zooˇanbz
13852
13853        ooˇanb
13854    "};
13855    let completion_marked_buffer = indoc! {"
13856        1. ooanb
13857        2. zooanb
13858        3. ooanbz
13859        4. zooanbz
13860
13861        <oo|anb>
13862    "};
13863    let expected = indoc! {"
13864        1. foo_and_barˇ
13865        2. zfoo_and_barˇ
13866        3. foo_and_barˇz
13867        4. zfoo_and_barˇz
13868
13869        foo_and_barˇ
13870    "};
13871    cx.set_state(initial_state);
13872    cx.update_editor(|editor, window, cx| {
13873        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13874    });
13875    handle_completion_request_with_insert_and_replace(
13876        &mut cx,
13877        completion_marked_buffer,
13878        vec![(completion_text, completion_text)],
13879        Arc::new(AtomicUsize::new(0)),
13880    )
13881    .await;
13882    cx.condition(|editor, _| editor.context_menu_visible())
13883        .await;
13884    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13885        editor
13886            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13887            .unwrap()
13888    });
13889    cx.assert_editor_state(expected);
13890    handle_resolve_completion_request(&mut cx, None).await;
13891    apply_additional_edits.await.unwrap();
13892}
13893
13894// This used to crash
13895#[gpui::test]
13896async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13897    init_test(cx, |_| {});
13898
13899    let buffer_text = indoc! {"
13900        fn main() {
13901            10.satu;
13902
13903            //
13904            // separate cursors so they open in different excerpts (manually reproducible)
13905            //
13906
13907            10.satu20;
13908        }
13909    "};
13910    let multibuffer_text_with_selections = indoc! {"
13911        fn main() {
13912            10.satuˇ;
13913
13914            //
13915
13916            //
13917
13918            10.satuˇ20;
13919        }
13920    "};
13921    let expected_multibuffer = indoc! {"
13922        fn main() {
13923            10.saturating_sub()ˇ;
13924
13925            //
13926
13927            //
13928
13929            10.saturating_sub()ˇ;
13930        }
13931    "};
13932
13933    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13934    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13935
13936    let fs = FakeFs::new(cx.executor());
13937    fs.insert_tree(
13938        path!("/a"),
13939        json!({
13940            "main.rs": buffer_text,
13941        }),
13942    )
13943    .await;
13944
13945    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13946    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13947    language_registry.add(rust_lang());
13948    let mut fake_servers = language_registry.register_fake_lsp(
13949        "Rust",
13950        FakeLspAdapter {
13951            capabilities: lsp::ServerCapabilities {
13952                completion_provider: Some(lsp::CompletionOptions {
13953                    resolve_provider: None,
13954                    ..lsp::CompletionOptions::default()
13955                }),
13956                ..lsp::ServerCapabilities::default()
13957            },
13958            ..FakeLspAdapter::default()
13959        },
13960    );
13961    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13962    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13963    let buffer = project
13964        .update(cx, |project, cx| {
13965            project.open_local_buffer(path!("/a/main.rs"), cx)
13966        })
13967        .await
13968        .unwrap();
13969
13970    let multi_buffer = cx.new(|cx| {
13971        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13972        multi_buffer.push_excerpts(
13973            buffer.clone(),
13974            [ExcerptRange::new(0..first_excerpt_end)],
13975            cx,
13976        );
13977        multi_buffer.push_excerpts(
13978            buffer.clone(),
13979            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13980            cx,
13981        );
13982        multi_buffer
13983    });
13984
13985    let editor = workspace
13986        .update(cx, |_, window, cx| {
13987            cx.new(|cx| {
13988                Editor::new(
13989                    EditorMode::Full {
13990                        scale_ui_elements_with_buffer_font_size: false,
13991                        show_active_line_background: false,
13992                        sized_by_content: false,
13993                    },
13994                    multi_buffer.clone(),
13995                    Some(project.clone()),
13996                    window,
13997                    cx,
13998                )
13999            })
14000        })
14001        .unwrap();
14002
14003    let pane = workspace
14004        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14005        .unwrap();
14006    pane.update_in(cx, |pane, window, cx| {
14007        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14008    });
14009
14010    let fake_server = fake_servers.next().await.unwrap();
14011
14012    editor.update_in(cx, |editor, window, cx| {
14013        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14014            s.select_ranges([
14015                Point::new(1, 11)..Point::new(1, 11),
14016                Point::new(7, 11)..Point::new(7, 11),
14017            ])
14018        });
14019
14020        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14021    });
14022
14023    editor.update_in(cx, |editor, window, cx| {
14024        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14025    });
14026
14027    fake_server
14028        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14029            let completion_item = lsp::CompletionItem {
14030                label: "saturating_sub()".into(),
14031                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14032                    lsp::InsertReplaceEdit {
14033                        new_text: "saturating_sub()".to_owned(),
14034                        insert: lsp::Range::new(
14035                            lsp::Position::new(7, 7),
14036                            lsp::Position::new(7, 11),
14037                        ),
14038                        replace: lsp::Range::new(
14039                            lsp::Position::new(7, 7),
14040                            lsp::Position::new(7, 13),
14041                        ),
14042                    },
14043                )),
14044                ..lsp::CompletionItem::default()
14045            };
14046
14047            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14048        })
14049        .next()
14050        .await
14051        .unwrap();
14052
14053    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14054        .await;
14055
14056    editor
14057        .update_in(cx, |editor, window, cx| {
14058            editor
14059                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14060                .unwrap()
14061        })
14062        .await
14063        .unwrap();
14064
14065    editor.update(cx, |editor, cx| {
14066        assert_text_with_selections(editor, expected_multibuffer, cx);
14067    })
14068}
14069
14070#[gpui::test]
14071async fn test_completion(cx: &mut TestAppContext) {
14072    init_test(cx, |_| {});
14073
14074    let mut cx = EditorLspTestContext::new_rust(
14075        lsp::ServerCapabilities {
14076            completion_provider: Some(lsp::CompletionOptions {
14077                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14078                resolve_provider: Some(true),
14079                ..Default::default()
14080            }),
14081            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14082            ..Default::default()
14083        },
14084        cx,
14085    )
14086    .await;
14087    let counter = Arc::new(AtomicUsize::new(0));
14088
14089    cx.set_state(indoc! {"
14090        oneˇ
14091        two
14092        three
14093    "});
14094    cx.simulate_keystroke(".");
14095    handle_completion_request(
14096        indoc! {"
14097            one.|<>
14098            two
14099            three
14100        "},
14101        vec!["first_completion", "second_completion"],
14102        true,
14103        counter.clone(),
14104        &mut cx,
14105    )
14106    .await;
14107    cx.condition(|editor, _| editor.context_menu_visible())
14108        .await;
14109    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14110
14111    let _handler = handle_signature_help_request(
14112        &mut cx,
14113        lsp::SignatureHelp {
14114            signatures: vec![lsp::SignatureInformation {
14115                label: "test signature".to_string(),
14116                documentation: None,
14117                parameters: Some(vec![lsp::ParameterInformation {
14118                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14119                    documentation: None,
14120                }]),
14121                active_parameter: None,
14122            }],
14123            active_signature: None,
14124            active_parameter: None,
14125        },
14126    );
14127    cx.update_editor(|editor, window, cx| {
14128        assert!(
14129            !editor.signature_help_state.is_shown(),
14130            "No signature help was called for"
14131        );
14132        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14133    });
14134    cx.run_until_parked();
14135    cx.update_editor(|editor, _, _| {
14136        assert!(
14137            !editor.signature_help_state.is_shown(),
14138            "No signature help should be shown when completions menu is open"
14139        );
14140    });
14141
14142    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14143        editor.context_menu_next(&Default::default(), window, cx);
14144        editor
14145            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14146            .unwrap()
14147    });
14148    cx.assert_editor_state(indoc! {"
14149        one.second_completionˇ
14150        two
14151        three
14152    "});
14153
14154    handle_resolve_completion_request(
14155        &mut cx,
14156        Some(vec![
14157            (
14158                //This overlaps with the primary completion edit which is
14159                //misbehavior from the LSP spec, test that we filter it out
14160                indoc! {"
14161                    one.second_ˇcompletion
14162                    two
14163                    threeˇ
14164                "},
14165                "overlapping additional edit",
14166            ),
14167            (
14168                indoc! {"
14169                    one.second_completion
14170                    two
14171                    threeˇ
14172                "},
14173                "\nadditional edit",
14174            ),
14175        ]),
14176    )
14177    .await;
14178    apply_additional_edits.await.unwrap();
14179    cx.assert_editor_state(indoc! {"
14180        one.second_completionˇ
14181        two
14182        three
14183        additional edit
14184    "});
14185
14186    cx.set_state(indoc! {"
14187        one.second_completion
14188        twoˇ
14189        threeˇ
14190        additional edit
14191    "});
14192    cx.simulate_keystroke(" ");
14193    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14194    cx.simulate_keystroke("s");
14195    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14196
14197    cx.assert_editor_state(indoc! {"
14198        one.second_completion
14199        two sˇ
14200        three sˇ
14201        additional edit
14202    "});
14203    handle_completion_request(
14204        indoc! {"
14205            one.second_completion
14206            two s
14207            three <s|>
14208            additional edit
14209        "},
14210        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14211        true,
14212        counter.clone(),
14213        &mut cx,
14214    )
14215    .await;
14216    cx.condition(|editor, _| editor.context_menu_visible())
14217        .await;
14218    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14219
14220    cx.simulate_keystroke("i");
14221
14222    handle_completion_request(
14223        indoc! {"
14224            one.second_completion
14225            two si
14226            three <si|>
14227            additional edit
14228        "},
14229        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14230        true,
14231        counter.clone(),
14232        &mut cx,
14233    )
14234    .await;
14235    cx.condition(|editor, _| editor.context_menu_visible())
14236        .await;
14237    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14238
14239    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14240        editor
14241            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14242            .unwrap()
14243    });
14244    cx.assert_editor_state(indoc! {"
14245        one.second_completion
14246        two sixth_completionˇ
14247        three sixth_completionˇ
14248        additional edit
14249    "});
14250
14251    apply_additional_edits.await.unwrap();
14252
14253    update_test_language_settings(&mut cx, |settings| {
14254        settings.defaults.show_completions_on_input = Some(false);
14255    });
14256    cx.set_state("editorˇ");
14257    cx.simulate_keystroke(".");
14258    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14259    cx.simulate_keystrokes("c l o");
14260    cx.assert_editor_state("editor.cloˇ");
14261    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14262    cx.update_editor(|editor, window, cx| {
14263        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14264    });
14265    handle_completion_request(
14266        "editor.<clo|>",
14267        vec!["close", "clobber"],
14268        true,
14269        counter.clone(),
14270        &mut cx,
14271    )
14272    .await;
14273    cx.condition(|editor, _| editor.context_menu_visible())
14274        .await;
14275    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14276
14277    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14278        editor
14279            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14280            .unwrap()
14281    });
14282    cx.assert_editor_state("editor.clobberˇ");
14283    handle_resolve_completion_request(&mut cx, None).await;
14284    apply_additional_edits.await.unwrap();
14285}
14286
14287#[gpui::test]
14288async fn test_completion_reuse(cx: &mut TestAppContext) {
14289    init_test(cx, |_| {});
14290
14291    let mut cx = EditorLspTestContext::new_rust(
14292        lsp::ServerCapabilities {
14293            completion_provider: Some(lsp::CompletionOptions {
14294                trigger_characters: Some(vec![".".to_string()]),
14295                ..Default::default()
14296            }),
14297            ..Default::default()
14298        },
14299        cx,
14300    )
14301    .await;
14302
14303    let counter = Arc::new(AtomicUsize::new(0));
14304    cx.set_state("objˇ");
14305    cx.simulate_keystroke(".");
14306
14307    // Initial completion request returns complete results
14308    let is_incomplete = false;
14309    handle_completion_request(
14310        "obj.|<>",
14311        vec!["a", "ab", "abc"],
14312        is_incomplete,
14313        counter.clone(),
14314        &mut cx,
14315    )
14316    .await;
14317    cx.run_until_parked();
14318    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14319    cx.assert_editor_state("obj.ˇ");
14320    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14321
14322    // Type "a" - filters existing completions
14323    cx.simulate_keystroke("a");
14324    cx.run_until_parked();
14325    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14326    cx.assert_editor_state("obj.aˇ");
14327    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14328
14329    // Type "b" - filters existing completions
14330    cx.simulate_keystroke("b");
14331    cx.run_until_parked();
14332    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14333    cx.assert_editor_state("obj.abˇ");
14334    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14335
14336    // Type "c" - filters existing completions
14337    cx.simulate_keystroke("c");
14338    cx.run_until_parked();
14339    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14340    cx.assert_editor_state("obj.abcˇ");
14341    check_displayed_completions(vec!["abc"], &mut cx);
14342
14343    // Backspace to delete "c" - filters existing completions
14344    cx.update_editor(|editor, window, cx| {
14345        editor.backspace(&Backspace, window, cx);
14346    });
14347    cx.run_until_parked();
14348    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14349    cx.assert_editor_state("obj.abˇ");
14350    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14351
14352    // Moving cursor to the left dismisses menu.
14353    cx.update_editor(|editor, window, cx| {
14354        editor.move_left(&MoveLeft, window, cx);
14355    });
14356    cx.run_until_parked();
14357    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14358    cx.assert_editor_state("obj.aˇb");
14359    cx.update_editor(|editor, _, _| {
14360        assert_eq!(editor.context_menu_visible(), false);
14361    });
14362
14363    // Type "b" - new request
14364    cx.simulate_keystroke("b");
14365    let is_incomplete = false;
14366    handle_completion_request(
14367        "obj.<ab|>a",
14368        vec!["ab", "abc"],
14369        is_incomplete,
14370        counter.clone(),
14371        &mut cx,
14372    )
14373    .await;
14374    cx.run_until_parked();
14375    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14376    cx.assert_editor_state("obj.abˇb");
14377    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14378
14379    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14380    cx.update_editor(|editor, window, cx| {
14381        editor.backspace(&Backspace, window, cx);
14382    });
14383    let is_incomplete = false;
14384    handle_completion_request(
14385        "obj.<a|>b",
14386        vec!["a", "ab", "abc"],
14387        is_incomplete,
14388        counter.clone(),
14389        &mut cx,
14390    )
14391    .await;
14392    cx.run_until_parked();
14393    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14394    cx.assert_editor_state("obj.aˇb");
14395    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14396
14397    // Backspace to delete "a" - dismisses menu.
14398    cx.update_editor(|editor, window, cx| {
14399        editor.backspace(&Backspace, window, cx);
14400    });
14401    cx.run_until_parked();
14402    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14403    cx.assert_editor_state("obj.ˇb");
14404    cx.update_editor(|editor, _, _| {
14405        assert_eq!(editor.context_menu_visible(), false);
14406    });
14407}
14408
14409#[gpui::test]
14410async fn test_word_completion(cx: &mut TestAppContext) {
14411    let lsp_fetch_timeout_ms = 10;
14412    init_test(cx, |language_settings| {
14413        language_settings.defaults.completions = Some(CompletionSettingsContent {
14414            words_min_length: Some(0),
14415            lsp_fetch_timeout_ms: Some(10),
14416            lsp_insert_mode: Some(LspInsertMode::Insert),
14417            ..Default::default()
14418        });
14419    });
14420
14421    let mut cx = EditorLspTestContext::new_rust(
14422        lsp::ServerCapabilities {
14423            completion_provider: Some(lsp::CompletionOptions {
14424                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14425                ..lsp::CompletionOptions::default()
14426            }),
14427            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14428            ..lsp::ServerCapabilities::default()
14429        },
14430        cx,
14431    )
14432    .await;
14433
14434    let throttle_completions = Arc::new(AtomicBool::new(false));
14435
14436    let lsp_throttle_completions = throttle_completions.clone();
14437    let _completion_requests_handler =
14438        cx.lsp
14439            .server
14440            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14441                let lsp_throttle_completions = lsp_throttle_completions.clone();
14442                let cx = cx.clone();
14443                async move {
14444                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14445                        cx.background_executor()
14446                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14447                            .await;
14448                    }
14449                    Ok(Some(lsp::CompletionResponse::Array(vec![
14450                        lsp::CompletionItem {
14451                            label: "first".into(),
14452                            ..lsp::CompletionItem::default()
14453                        },
14454                        lsp::CompletionItem {
14455                            label: "last".into(),
14456                            ..lsp::CompletionItem::default()
14457                        },
14458                    ])))
14459                }
14460            });
14461
14462    cx.set_state(indoc! {"
14463        oneˇ
14464        two
14465        three
14466    "});
14467    cx.simulate_keystroke(".");
14468    cx.executor().run_until_parked();
14469    cx.condition(|editor, _| editor.context_menu_visible())
14470        .await;
14471    cx.update_editor(|editor, window, cx| {
14472        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14473        {
14474            assert_eq!(
14475                completion_menu_entries(menu),
14476                &["first", "last"],
14477                "When LSP server is fast to reply, no fallback word completions are used"
14478            );
14479        } else {
14480            panic!("expected completion menu to be open");
14481        }
14482        editor.cancel(&Cancel, window, cx);
14483    });
14484    cx.executor().run_until_parked();
14485    cx.condition(|editor, _| !editor.context_menu_visible())
14486        .await;
14487
14488    throttle_completions.store(true, atomic::Ordering::Release);
14489    cx.simulate_keystroke(".");
14490    cx.executor()
14491        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14492    cx.executor().run_until_parked();
14493    cx.condition(|editor, _| editor.context_menu_visible())
14494        .await;
14495    cx.update_editor(|editor, _, _| {
14496        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14497        {
14498            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14499                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14500        } else {
14501            panic!("expected completion menu to be open");
14502        }
14503    });
14504}
14505
14506#[gpui::test]
14507async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14508    init_test(cx, |language_settings| {
14509        language_settings.defaults.completions = Some(CompletionSettingsContent {
14510            words: Some(WordsCompletionMode::Enabled),
14511            words_min_length: Some(0),
14512            lsp_insert_mode: Some(LspInsertMode::Insert),
14513            ..Default::default()
14514        });
14515    });
14516
14517    let mut cx = EditorLspTestContext::new_rust(
14518        lsp::ServerCapabilities {
14519            completion_provider: Some(lsp::CompletionOptions {
14520                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14521                ..lsp::CompletionOptions::default()
14522            }),
14523            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14524            ..lsp::ServerCapabilities::default()
14525        },
14526        cx,
14527    )
14528    .await;
14529
14530    let _completion_requests_handler =
14531        cx.lsp
14532            .server
14533            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14534                Ok(Some(lsp::CompletionResponse::Array(vec![
14535                    lsp::CompletionItem {
14536                        label: "first".into(),
14537                        ..lsp::CompletionItem::default()
14538                    },
14539                    lsp::CompletionItem {
14540                        label: "last".into(),
14541                        ..lsp::CompletionItem::default()
14542                    },
14543                ])))
14544            });
14545
14546    cx.set_state(indoc! {"ˇ
14547        first
14548        last
14549        second
14550    "});
14551    cx.simulate_keystroke(".");
14552    cx.executor().run_until_parked();
14553    cx.condition(|editor, _| editor.context_menu_visible())
14554        .await;
14555    cx.update_editor(|editor, _, _| {
14556        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14557        {
14558            assert_eq!(
14559                completion_menu_entries(menu),
14560                &["first", "last", "second"],
14561                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14562            );
14563        } else {
14564            panic!("expected completion menu to be open");
14565        }
14566    });
14567}
14568
14569#[gpui::test]
14570async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14571    init_test(cx, |language_settings| {
14572        language_settings.defaults.completions = Some(CompletionSettingsContent {
14573            words: Some(WordsCompletionMode::Disabled),
14574            words_min_length: Some(0),
14575            lsp_insert_mode: Some(LspInsertMode::Insert),
14576            ..Default::default()
14577        });
14578    });
14579
14580    let mut cx = EditorLspTestContext::new_rust(
14581        lsp::ServerCapabilities {
14582            completion_provider: Some(lsp::CompletionOptions {
14583                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14584                ..lsp::CompletionOptions::default()
14585            }),
14586            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14587            ..lsp::ServerCapabilities::default()
14588        },
14589        cx,
14590    )
14591    .await;
14592
14593    let _completion_requests_handler =
14594        cx.lsp
14595            .server
14596            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14597                panic!("LSP completions should not be queried when dealing with word completions")
14598            });
14599
14600    cx.set_state(indoc! {"ˇ
14601        first
14602        last
14603        second
14604    "});
14605    cx.update_editor(|editor, window, cx| {
14606        editor.show_word_completions(&ShowWordCompletions, window, cx);
14607    });
14608    cx.executor().run_until_parked();
14609    cx.condition(|editor, _| editor.context_menu_visible())
14610        .await;
14611    cx.update_editor(|editor, _, _| {
14612        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14613        {
14614            assert_eq!(
14615                completion_menu_entries(menu),
14616                &["first", "last", "second"],
14617                "`ShowWordCompletions` action should show word completions"
14618            );
14619        } else {
14620            panic!("expected completion menu to be open");
14621        }
14622    });
14623
14624    cx.simulate_keystroke("l");
14625    cx.executor().run_until_parked();
14626    cx.condition(|editor, _| editor.context_menu_visible())
14627        .await;
14628    cx.update_editor(|editor, _, _| {
14629        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14630        {
14631            assert_eq!(
14632                completion_menu_entries(menu),
14633                &["last"],
14634                "After showing word completions, further editing should filter them and not query the LSP"
14635            );
14636        } else {
14637            panic!("expected completion menu to be open");
14638        }
14639    });
14640}
14641
14642#[gpui::test]
14643async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14644    init_test(cx, |language_settings| {
14645        language_settings.defaults.completions = Some(CompletionSettingsContent {
14646            words_min_length: Some(0),
14647            lsp: Some(false),
14648            lsp_insert_mode: Some(LspInsertMode::Insert),
14649            ..Default::default()
14650        });
14651    });
14652
14653    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14654
14655    cx.set_state(indoc! {"ˇ
14656        0_usize
14657        let
14658        33
14659        4.5f32
14660    "});
14661    cx.update_editor(|editor, window, cx| {
14662        editor.show_completions(&ShowCompletions::default(), window, cx);
14663    });
14664    cx.executor().run_until_parked();
14665    cx.condition(|editor, _| editor.context_menu_visible())
14666        .await;
14667    cx.update_editor(|editor, window, cx| {
14668        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14669        {
14670            assert_eq!(
14671                completion_menu_entries(menu),
14672                &["let"],
14673                "With no digits in the completion query, no digits should be in the word completions"
14674            );
14675        } else {
14676            panic!("expected completion menu to be open");
14677        }
14678        editor.cancel(&Cancel, window, cx);
14679    });
14680
14681    cx.set_state(indoc! {"14682        0_usize
14683        let
14684        3
14685        33.35f32
14686    "});
14687    cx.update_editor(|editor, window, cx| {
14688        editor.show_completions(&ShowCompletions::default(), window, cx);
14689    });
14690    cx.executor().run_until_parked();
14691    cx.condition(|editor, _| editor.context_menu_visible())
14692        .await;
14693    cx.update_editor(|editor, _, _| {
14694        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14695        {
14696            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14697                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14698        } else {
14699            panic!("expected completion menu to be open");
14700        }
14701    });
14702}
14703
14704#[gpui::test]
14705async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14706    init_test(cx, |language_settings| {
14707        language_settings.defaults.completions = Some(CompletionSettingsContent {
14708            words: Some(WordsCompletionMode::Enabled),
14709            words_min_length: Some(3),
14710            lsp_insert_mode: Some(LspInsertMode::Insert),
14711            ..Default::default()
14712        });
14713    });
14714
14715    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14716    cx.set_state(indoc! {"ˇ
14717        wow
14718        wowen
14719        wowser
14720    "});
14721    cx.simulate_keystroke("w");
14722    cx.executor().run_until_parked();
14723    cx.update_editor(|editor, _, _| {
14724        if editor.context_menu.borrow_mut().is_some() {
14725            panic!(
14726                "expected completion menu to be hidden, as words completion threshold is not met"
14727            );
14728        }
14729    });
14730
14731    cx.update_editor(|editor, window, cx| {
14732        editor.show_word_completions(&ShowWordCompletions, window, cx);
14733    });
14734    cx.executor().run_until_parked();
14735    cx.update_editor(|editor, window, cx| {
14736        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14737        {
14738            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");
14739        } else {
14740            panic!("expected completion menu to be open after the word completions are called with an action");
14741        }
14742
14743        editor.cancel(&Cancel, window, cx);
14744    });
14745    cx.update_editor(|editor, _, _| {
14746        if editor.context_menu.borrow_mut().is_some() {
14747            panic!("expected completion menu to be hidden after canceling");
14748        }
14749    });
14750
14751    cx.simulate_keystroke("o");
14752    cx.executor().run_until_parked();
14753    cx.update_editor(|editor, _, _| {
14754        if editor.context_menu.borrow_mut().is_some() {
14755            panic!(
14756                "expected completion menu to be hidden, as words completion threshold is not met still"
14757            );
14758        }
14759    });
14760
14761    cx.simulate_keystroke("w");
14762    cx.executor().run_until_parked();
14763    cx.update_editor(|editor, _, _| {
14764        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14765        {
14766            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14767        } else {
14768            panic!("expected completion menu to be open after the word completions threshold is met");
14769        }
14770    });
14771}
14772
14773#[gpui::test]
14774async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14775    init_test(cx, |language_settings| {
14776        language_settings.defaults.completions = Some(CompletionSettingsContent {
14777            words: Some(WordsCompletionMode::Enabled),
14778            words_min_length: Some(0),
14779            lsp_insert_mode: Some(LspInsertMode::Insert),
14780            ..Default::default()
14781        });
14782    });
14783
14784    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14785    cx.update_editor(|editor, _, _| {
14786        editor.disable_word_completions();
14787    });
14788    cx.set_state(indoc! {"ˇ
14789        wow
14790        wowen
14791        wowser
14792    "});
14793    cx.simulate_keystroke("w");
14794    cx.executor().run_until_parked();
14795    cx.update_editor(|editor, _, _| {
14796        if editor.context_menu.borrow_mut().is_some() {
14797            panic!(
14798                "expected completion menu to be hidden, as words completion are disabled for this editor"
14799            );
14800        }
14801    });
14802
14803    cx.update_editor(|editor, window, cx| {
14804        editor.show_word_completions(&ShowWordCompletions, window, cx);
14805    });
14806    cx.executor().run_until_parked();
14807    cx.update_editor(|editor, _, _| {
14808        if editor.context_menu.borrow_mut().is_some() {
14809            panic!(
14810                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14811            );
14812        }
14813    });
14814}
14815
14816fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14817    let position = || lsp::Position {
14818        line: params.text_document_position.position.line,
14819        character: params.text_document_position.position.character,
14820    };
14821    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14822        range: lsp::Range {
14823            start: position(),
14824            end: position(),
14825        },
14826        new_text: text.to_string(),
14827    }))
14828}
14829
14830#[gpui::test]
14831async fn test_multiline_completion(cx: &mut TestAppContext) {
14832    init_test(cx, |_| {});
14833
14834    let fs = FakeFs::new(cx.executor());
14835    fs.insert_tree(
14836        path!("/a"),
14837        json!({
14838            "main.ts": "a",
14839        }),
14840    )
14841    .await;
14842
14843    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14844    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14845    let typescript_language = Arc::new(Language::new(
14846        LanguageConfig {
14847            name: "TypeScript".into(),
14848            matcher: LanguageMatcher {
14849                path_suffixes: vec!["ts".to_string()],
14850                ..LanguageMatcher::default()
14851            },
14852            line_comments: vec!["// ".into()],
14853            ..LanguageConfig::default()
14854        },
14855        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14856    ));
14857    language_registry.add(typescript_language.clone());
14858    let mut fake_servers = language_registry.register_fake_lsp(
14859        "TypeScript",
14860        FakeLspAdapter {
14861            capabilities: lsp::ServerCapabilities {
14862                completion_provider: Some(lsp::CompletionOptions {
14863                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14864                    ..lsp::CompletionOptions::default()
14865                }),
14866                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14867                ..lsp::ServerCapabilities::default()
14868            },
14869            // Emulate vtsls label generation
14870            label_for_completion: Some(Box::new(|item, _| {
14871                let text = if let Some(description) = item
14872                    .label_details
14873                    .as_ref()
14874                    .and_then(|label_details| label_details.description.as_ref())
14875                {
14876                    format!("{} {}", item.label, description)
14877                } else if let Some(detail) = &item.detail {
14878                    format!("{} {}", item.label, detail)
14879                } else {
14880                    item.label.clone()
14881                };
14882                Some(language::CodeLabel::plain(text, None))
14883            })),
14884            ..FakeLspAdapter::default()
14885        },
14886    );
14887    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14888    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14889    let worktree_id = workspace
14890        .update(cx, |workspace, _window, cx| {
14891            workspace.project().update(cx, |project, cx| {
14892                project.worktrees(cx).next().unwrap().read(cx).id()
14893            })
14894        })
14895        .unwrap();
14896    let _buffer = project
14897        .update(cx, |project, cx| {
14898            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14899        })
14900        .await
14901        .unwrap();
14902    let editor = workspace
14903        .update(cx, |workspace, window, cx| {
14904            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
14905        })
14906        .unwrap()
14907        .await
14908        .unwrap()
14909        .downcast::<Editor>()
14910        .unwrap();
14911    let fake_server = fake_servers.next().await.unwrap();
14912
14913    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14914    let multiline_label_2 = "a\nb\nc\n";
14915    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14916    let multiline_description = "d\ne\nf\n";
14917    let multiline_detail_2 = "g\nh\ni\n";
14918
14919    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14920        move |params, _| async move {
14921            Ok(Some(lsp::CompletionResponse::Array(vec![
14922                lsp::CompletionItem {
14923                    label: multiline_label.to_string(),
14924                    text_edit: gen_text_edit(&params, "new_text_1"),
14925                    ..lsp::CompletionItem::default()
14926                },
14927                lsp::CompletionItem {
14928                    label: "single line label 1".to_string(),
14929                    detail: Some(multiline_detail.to_string()),
14930                    text_edit: gen_text_edit(&params, "new_text_2"),
14931                    ..lsp::CompletionItem::default()
14932                },
14933                lsp::CompletionItem {
14934                    label: "single line label 2".to_string(),
14935                    label_details: Some(lsp::CompletionItemLabelDetails {
14936                        description: Some(multiline_description.to_string()),
14937                        detail: None,
14938                    }),
14939                    text_edit: gen_text_edit(&params, "new_text_2"),
14940                    ..lsp::CompletionItem::default()
14941                },
14942                lsp::CompletionItem {
14943                    label: multiline_label_2.to_string(),
14944                    detail: Some(multiline_detail_2.to_string()),
14945                    text_edit: gen_text_edit(&params, "new_text_3"),
14946                    ..lsp::CompletionItem::default()
14947                },
14948                lsp::CompletionItem {
14949                    label: "Label with many     spaces and \t but without newlines".to_string(),
14950                    detail: Some(
14951                        "Details with many     spaces and \t but without newlines".to_string(),
14952                    ),
14953                    text_edit: gen_text_edit(&params, "new_text_4"),
14954                    ..lsp::CompletionItem::default()
14955                },
14956            ])))
14957        },
14958    );
14959
14960    editor.update_in(cx, |editor, window, cx| {
14961        cx.focus_self(window);
14962        editor.move_to_end(&MoveToEnd, window, cx);
14963        editor.handle_input(".", window, cx);
14964    });
14965    cx.run_until_parked();
14966    completion_handle.next().await.unwrap();
14967
14968    editor.update(cx, |editor, _| {
14969        assert!(editor.context_menu_visible());
14970        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14971        {
14972            let completion_labels = menu
14973                .completions
14974                .borrow()
14975                .iter()
14976                .map(|c| c.label.text.clone())
14977                .collect::<Vec<_>>();
14978            assert_eq!(
14979                completion_labels,
14980                &[
14981                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14982                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14983                    "single line label 2 d e f ",
14984                    "a b c g h i ",
14985                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14986                ],
14987                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14988            );
14989
14990            for completion in menu
14991                .completions
14992                .borrow()
14993                .iter() {
14994                    assert_eq!(
14995                        completion.label.filter_range,
14996                        0..completion.label.text.len(),
14997                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14998                    );
14999                }
15000        } else {
15001            panic!("expected completion menu to be open");
15002        }
15003    });
15004}
15005
15006#[gpui::test]
15007async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15008    init_test(cx, |_| {});
15009    let mut cx = EditorLspTestContext::new_rust(
15010        lsp::ServerCapabilities {
15011            completion_provider: Some(lsp::CompletionOptions {
15012                trigger_characters: Some(vec![".".to_string()]),
15013                ..Default::default()
15014            }),
15015            ..Default::default()
15016        },
15017        cx,
15018    )
15019    .await;
15020    cx.lsp
15021        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15022            Ok(Some(lsp::CompletionResponse::Array(vec![
15023                lsp::CompletionItem {
15024                    label: "first".into(),
15025                    ..Default::default()
15026                },
15027                lsp::CompletionItem {
15028                    label: "last".into(),
15029                    ..Default::default()
15030                },
15031            ])))
15032        });
15033    cx.set_state("variableˇ");
15034    cx.simulate_keystroke(".");
15035    cx.executor().run_until_parked();
15036
15037    cx.update_editor(|editor, _, _| {
15038        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15039        {
15040            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15041        } else {
15042            panic!("expected completion menu to be open");
15043        }
15044    });
15045
15046    cx.update_editor(|editor, window, cx| {
15047        editor.move_page_down(&MovePageDown::default(), window, cx);
15048        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15049        {
15050            assert!(
15051                menu.selected_item == 1,
15052                "expected PageDown to select the last item from the context menu"
15053            );
15054        } else {
15055            panic!("expected completion menu to stay open after PageDown");
15056        }
15057    });
15058
15059    cx.update_editor(|editor, window, cx| {
15060        editor.move_page_up(&MovePageUp::default(), window, cx);
15061        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15062        {
15063            assert!(
15064                menu.selected_item == 0,
15065                "expected PageUp to select the first item from the context menu"
15066            );
15067        } else {
15068            panic!("expected completion menu to stay open after PageUp");
15069        }
15070    });
15071}
15072
15073#[gpui::test]
15074async fn test_as_is_completions(cx: &mut TestAppContext) {
15075    init_test(cx, |_| {});
15076    let mut cx = EditorLspTestContext::new_rust(
15077        lsp::ServerCapabilities {
15078            completion_provider: Some(lsp::CompletionOptions {
15079                ..Default::default()
15080            }),
15081            ..Default::default()
15082        },
15083        cx,
15084    )
15085    .await;
15086    cx.lsp
15087        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15088            Ok(Some(lsp::CompletionResponse::Array(vec![
15089                lsp::CompletionItem {
15090                    label: "unsafe".into(),
15091                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15092                        range: lsp::Range {
15093                            start: lsp::Position {
15094                                line: 1,
15095                                character: 2,
15096                            },
15097                            end: lsp::Position {
15098                                line: 1,
15099                                character: 3,
15100                            },
15101                        },
15102                        new_text: "unsafe".to_string(),
15103                    })),
15104                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15105                    ..Default::default()
15106                },
15107            ])))
15108        });
15109    cx.set_state("fn a() {}\n");
15110    cx.executor().run_until_parked();
15111    cx.update_editor(|editor, window, cx| {
15112        editor.show_completions(
15113            &ShowCompletions {
15114                trigger: Some("\n".into()),
15115            },
15116            window,
15117            cx,
15118        );
15119    });
15120    cx.executor().run_until_parked();
15121
15122    cx.update_editor(|editor, window, cx| {
15123        editor.confirm_completion(&Default::default(), window, cx)
15124    });
15125    cx.executor().run_until_parked();
15126    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15127}
15128
15129#[gpui::test]
15130async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15131    init_test(cx, |_| {});
15132    let language =
15133        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15134    let mut cx = EditorLspTestContext::new(
15135        language,
15136        lsp::ServerCapabilities {
15137            completion_provider: Some(lsp::CompletionOptions {
15138                ..lsp::CompletionOptions::default()
15139            }),
15140            ..lsp::ServerCapabilities::default()
15141        },
15142        cx,
15143    )
15144    .await;
15145
15146    cx.set_state(
15147        "#ifndef BAR_H
15148#define BAR_H
15149
15150#include <stdbool.h>
15151
15152int fn_branch(bool do_branch1, bool do_branch2);
15153
15154#endif // BAR_H
15155ˇ",
15156    );
15157    cx.executor().run_until_parked();
15158    cx.update_editor(|editor, window, cx| {
15159        editor.handle_input("#", window, cx);
15160    });
15161    cx.executor().run_until_parked();
15162    cx.update_editor(|editor, window, cx| {
15163        editor.handle_input("i", window, cx);
15164    });
15165    cx.executor().run_until_parked();
15166    cx.update_editor(|editor, window, cx| {
15167        editor.handle_input("n", window, cx);
15168    });
15169    cx.executor().run_until_parked();
15170    cx.assert_editor_state(
15171        "#ifndef BAR_H
15172#define BAR_H
15173
15174#include <stdbool.h>
15175
15176int fn_branch(bool do_branch1, bool do_branch2);
15177
15178#endif // BAR_H
15179#inˇ",
15180    );
15181
15182    cx.lsp
15183        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15184            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15185                is_incomplete: false,
15186                item_defaults: None,
15187                items: vec![lsp::CompletionItem {
15188                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15189                    label_details: Some(lsp::CompletionItemLabelDetails {
15190                        detail: Some("header".to_string()),
15191                        description: None,
15192                    }),
15193                    label: " include".to_string(),
15194                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15195                        range: lsp::Range {
15196                            start: lsp::Position {
15197                                line: 8,
15198                                character: 1,
15199                            },
15200                            end: lsp::Position {
15201                                line: 8,
15202                                character: 1,
15203                            },
15204                        },
15205                        new_text: "include \"$0\"".to_string(),
15206                    })),
15207                    sort_text: Some("40b67681include".to_string()),
15208                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15209                    filter_text: Some("include".to_string()),
15210                    insert_text: Some("include \"$0\"".to_string()),
15211                    ..lsp::CompletionItem::default()
15212                }],
15213            })))
15214        });
15215    cx.update_editor(|editor, window, cx| {
15216        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15217    });
15218    cx.executor().run_until_parked();
15219    cx.update_editor(|editor, window, cx| {
15220        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15221    });
15222    cx.executor().run_until_parked();
15223    cx.assert_editor_state(
15224        "#ifndef BAR_H
15225#define BAR_H
15226
15227#include <stdbool.h>
15228
15229int fn_branch(bool do_branch1, bool do_branch2);
15230
15231#endif // BAR_H
15232#include \"ˇ\"",
15233    );
15234
15235    cx.lsp
15236        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15237            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15238                is_incomplete: true,
15239                item_defaults: None,
15240                items: vec![lsp::CompletionItem {
15241                    kind: Some(lsp::CompletionItemKind::FILE),
15242                    label: "AGL/".to_string(),
15243                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15244                        range: lsp::Range {
15245                            start: lsp::Position {
15246                                line: 8,
15247                                character: 10,
15248                            },
15249                            end: lsp::Position {
15250                                line: 8,
15251                                character: 11,
15252                            },
15253                        },
15254                        new_text: "AGL/".to_string(),
15255                    })),
15256                    sort_text: Some("40b67681AGL/".to_string()),
15257                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15258                    filter_text: Some("AGL/".to_string()),
15259                    insert_text: Some("AGL/".to_string()),
15260                    ..lsp::CompletionItem::default()
15261                }],
15262            })))
15263        });
15264    cx.update_editor(|editor, window, cx| {
15265        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15266    });
15267    cx.executor().run_until_parked();
15268    cx.update_editor(|editor, window, cx| {
15269        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15270    });
15271    cx.executor().run_until_parked();
15272    cx.assert_editor_state(
15273        r##"#ifndef BAR_H
15274#define BAR_H
15275
15276#include <stdbool.h>
15277
15278int fn_branch(bool do_branch1, bool do_branch2);
15279
15280#endif // BAR_H
15281#include "AGL/ˇ"##,
15282    );
15283
15284    cx.update_editor(|editor, window, cx| {
15285        editor.handle_input("\"", window, cx);
15286    });
15287    cx.executor().run_until_parked();
15288    cx.assert_editor_state(
15289        r##"#ifndef BAR_H
15290#define BAR_H
15291
15292#include <stdbool.h>
15293
15294int fn_branch(bool do_branch1, bool do_branch2);
15295
15296#endif // BAR_H
15297#include "AGL/"ˇ"##,
15298    );
15299}
15300
15301#[gpui::test]
15302async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15303    init_test(cx, |_| {});
15304
15305    let mut cx = EditorLspTestContext::new_rust(
15306        lsp::ServerCapabilities {
15307            completion_provider: Some(lsp::CompletionOptions {
15308                trigger_characters: Some(vec![".".to_string()]),
15309                resolve_provider: Some(true),
15310                ..Default::default()
15311            }),
15312            ..Default::default()
15313        },
15314        cx,
15315    )
15316    .await;
15317
15318    cx.set_state("fn main() { let a = 2ˇ; }");
15319    cx.simulate_keystroke(".");
15320    let completion_item = lsp::CompletionItem {
15321        label: "Some".into(),
15322        kind: Some(lsp::CompletionItemKind::SNIPPET),
15323        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15324        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15325            kind: lsp::MarkupKind::Markdown,
15326            value: "```rust\nSome(2)\n```".to_string(),
15327        })),
15328        deprecated: Some(false),
15329        sort_text: Some("Some".to_string()),
15330        filter_text: Some("Some".to_string()),
15331        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15332        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15333            range: lsp::Range {
15334                start: lsp::Position {
15335                    line: 0,
15336                    character: 22,
15337                },
15338                end: lsp::Position {
15339                    line: 0,
15340                    character: 22,
15341                },
15342            },
15343            new_text: "Some(2)".to_string(),
15344        })),
15345        additional_text_edits: Some(vec![lsp::TextEdit {
15346            range: lsp::Range {
15347                start: lsp::Position {
15348                    line: 0,
15349                    character: 20,
15350                },
15351                end: lsp::Position {
15352                    line: 0,
15353                    character: 22,
15354                },
15355            },
15356            new_text: "".to_string(),
15357        }]),
15358        ..Default::default()
15359    };
15360
15361    let closure_completion_item = completion_item.clone();
15362    let counter = Arc::new(AtomicUsize::new(0));
15363    let counter_clone = counter.clone();
15364    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15365        let task_completion_item = closure_completion_item.clone();
15366        counter_clone.fetch_add(1, atomic::Ordering::Release);
15367        async move {
15368            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15369                is_incomplete: true,
15370                item_defaults: None,
15371                items: vec![task_completion_item],
15372            })))
15373        }
15374    });
15375
15376    cx.condition(|editor, _| editor.context_menu_visible())
15377        .await;
15378    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15379    assert!(request.next().await.is_some());
15380    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15381
15382    cx.simulate_keystrokes("S o m");
15383    cx.condition(|editor, _| editor.context_menu_visible())
15384        .await;
15385    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15386    assert!(request.next().await.is_some());
15387    assert!(request.next().await.is_some());
15388    assert!(request.next().await.is_some());
15389    request.close();
15390    assert!(request.next().await.is_none());
15391    assert_eq!(
15392        counter.load(atomic::Ordering::Acquire),
15393        4,
15394        "With the completions menu open, only one LSP request should happen per input"
15395    );
15396}
15397
15398#[gpui::test]
15399async fn test_toggle_comment(cx: &mut TestAppContext) {
15400    init_test(cx, |_| {});
15401    let mut cx = EditorTestContext::new(cx).await;
15402    let language = Arc::new(Language::new(
15403        LanguageConfig {
15404            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15405            ..Default::default()
15406        },
15407        Some(tree_sitter_rust::LANGUAGE.into()),
15408    ));
15409    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15410
15411    // If multiple selections intersect a line, the line is only toggled once.
15412    cx.set_state(indoc! {"
15413        fn a() {
15414            «//b();
15415            ˇ»// «c();
15416            //ˇ»  d();
15417        }
15418    "});
15419
15420    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15421
15422    cx.assert_editor_state(indoc! {"
15423        fn a() {
15424            «b();
15425            c();
15426            ˇ» d();
15427        }
15428    "});
15429
15430    // The comment prefix is inserted at the same column for every line in a
15431    // selection.
15432    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15433
15434    cx.assert_editor_state(indoc! {"
15435        fn a() {
15436            // «b();
15437            // c();
15438            ˇ»//  d();
15439        }
15440    "});
15441
15442    // If a selection ends at the beginning of a line, that line is not toggled.
15443    cx.set_selections_state(indoc! {"
15444        fn a() {
15445            // b();
15446            «// c();
15447        ˇ»    //  d();
15448        }
15449    "});
15450
15451    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15452
15453    cx.assert_editor_state(indoc! {"
15454        fn a() {
15455            // b();
15456            «c();
15457        ˇ»    //  d();
15458        }
15459    "});
15460
15461    // If a selection span a single line and is empty, the line is toggled.
15462    cx.set_state(indoc! {"
15463        fn a() {
15464            a();
15465            b();
15466        ˇ
15467        }
15468    "});
15469
15470    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15471
15472    cx.assert_editor_state(indoc! {"
15473        fn a() {
15474            a();
15475            b();
15476        //•ˇ
15477        }
15478    "});
15479
15480    // If a selection span multiple lines, empty lines are not toggled.
15481    cx.set_state(indoc! {"
15482        fn a() {
15483            «a();
15484
15485            c();ˇ»
15486        }
15487    "});
15488
15489    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15490
15491    cx.assert_editor_state(indoc! {"
15492        fn a() {
15493            // «a();
15494
15495            // c();ˇ»
15496        }
15497    "});
15498
15499    // If a selection includes multiple comment prefixes, all lines are uncommented.
15500    cx.set_state(indoc! {"
15501        fn a() {
15502            «// a();
15503            /// b();
15504            //! c();ˇ»
15505        }
15506    "});
15507
15508    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15509
15510    cx.assert_editor_state(indoc! {"
15511        fn a() {
15512            «a();
15513            b();
15514            c();ˇ»
15515        }
15516    "});
15517}
15518
15519#[gpui::test]
15520async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15521    init_test(cx, |_| {});
15522    let mut cx = EditorTestContext::new(cx).await;
15523    let language = Arc::new(Language::new(
15524        LanguageConfig {
15525            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15526            ..Default::default()
15527        },
15528        Some(tree_sitter_rust::LANGUAGE.into()),
15529    ));
15530    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15531
15532    let toggle_comments = &ToggleComments {
15533        advance_downwards: false,
15534        ignore_indent: true,
15535    };
15536
15537    // If multiple selections intersect a line, the line is only toggled once.
15538    cx.set_state(indoc! {"
15539        fn a() {
15540        //    «b();
15541        //    c();
15542        //    ˇ» d();
15543        }
15544    "});
15545
15546    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15547
15548    cx.assert_editor_state(indoc! {"
15549        fn a() {
15550            «b();
15551            c();
15552            ˇ» d();
15553        }
15554    "});
15555
15556    // The comment prefix is inserted at the beginning of each line
15557    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15558
15559    cx.assert_editor_state(indoc! {"
15560        fn a() {
15561        //    «b();
15562        //    c();
15563        //    ˇ» d();
15564        }
15565    "});
15566
15567    // If a selection ends at the beginning of a line, that line is not toggled.
15568    cx.set_selections_state(indoc! {"
15569        fn a() {
15570        //    b();
15571        //    «c();
15572        ˇ»//     d();
15573        }
15574    "});
15575
15576    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15577
15578    cx.assert_editor_state(indoc! {"
15579        fn a() {
15580        //    b();
15581            «c();
15582        ˇ»//     d();
15583        }
15584    "});
15585
15586    // If a selection span a single line and is empty, the line is toggled.
15587    cx.set_state(indoc! {"
15588        fn a() {
15589            a();
15590            b();
15591        ˇ
15592        }
15593    "});
15594
15595    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15596
15597    cx.assert_editor_state(indoc! {"
15598        fn a() {
15599            a();
15600            b();
15601        //ˇ
15602        }
15603    "});
15604
15605    // If a selection span multiple lines, empty lines are not toggled.
15606    cx.set_state(indoc! {"
15607        fn a() {
15608            «a();
15609
15610            c();ˇ»
15611        }
15612    "});
15613
15614    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15615
15616    cx.assert_editor_state(indoc! {"
15617        fn a() {
15618        //    «a();
15619
15620        //    c();ˇ»
15621        }
15622    "});
15623
15624    // If a selection includes multiple comment prefixes, all lines are uncommented.
15625    cx.set_state(indoc! {"
15626        fn a() {
15627        //    «a();
15628        ///    b();
15629        //!    c();ˇ»
15630        }
15631    "});
15632
15633    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15634
15635    cx.assert_editor_state(indoc! {"
15636        fn a() {
15637            «a();
15638            b();
15639            c();ˇ»
15640        }
15641    "});
15642}
15643
15644#[gpui::test]
15645async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15646    init_test(cx, |_| {});
15647
15648    let language = Arc::new(Language::new(
15649        LanguageConfig {
15650            line_comments: vec!["// ".into()],
15651            ..Default::default()
15652        },
15653        Some(tree_sitter_rust::LANGUAGE.into()),
15654    ));
15655
15656    let mut cx = EditorTestContext::new(cx).await;
15657
15658    cx.language_registry().add(language.clone());
15659    cx.update_buffer(|buffer, cx| {
15660        buffer.set_language(Some(language), cx);
15661    });
15662
15663    let toggle_comments = &ToggleComments {
15664        advance_downwards: true,
15665        ignore_indent: false,
15666    };
15667
15668    // Single cursor on one line -> advance
15669    // Cursor moves horizontally 3 characters as well on non-blank line
15670    cx.set_state(indoc!(
15671        "fn a() {
15672             ˇdog();
15673             cat();
15674        }"
15675    ));
15676    cx.update_editor(|editor, window, cx| {
15677        editor.toggle_comments(toggle_comments, window, cx);
15678    });
15679    cx.assert_editor_state(indoc!(
15680        "fn a() {
15681             // dog();
15682             catˇ();
15683        }"
15684    ));
15685
15686    // Single selection on one line -> don't advance
15687    cx.set_state(indoc!(
15688        "fn a() {
15689             «dog()ˇ»;
15690             cat();
15691        }"
15692    ));
15693    cx.update_editor(|editor, window, cx| {
15694        editor.toggle_comments(toggle_comments, window, cx);
15695    });
15696    cx.assert_editor_state(indoc!(
15697        "fn a() {
15698             // «dog()ˇ»;
15699             cat();
15700        }"
15701    ));
15702
15703    // Multiple cursors on one line -> advance
15704    cx.set_state(indoc!(
15705        "fn a() {
15706             ˇdˇog();
15707             cat();
15708        }"
15709    ));
15710    cx.update_editor(|editor, window, cx| {
15711        editor.toggle_comments(toggle_comments, window, cx);
15712    });
15713    cx.assert_editor_state(indoc!(
15714        "fn a() {
15715             // dog();
15716             catˇ(ˇ);
15717        }"
15718    ));
15719
15720    // Multiple cursors on one line, with selection -> don't advance
15721    cx.set_state(indoc!(
15722        "fn a() {
15723             ˇdˇog«()ˇ»;
15724             cat();
15725        }"
15726    ));
15727    cx.update_editor(|editor, window, cx| {
15728        editor.toggle_comments(toggle_comments, window, cx);
15729    });
15730    cx.assert_editor_state(indoc!(
15731        "fn a() {
15732             // ˇdˇog«()ˇ»;
15733             cat();
15734        }"
15735    ));
15736
15737    // Single cursor on one line -> advance
15738    // Cursor moves to column 0 on blank line
15739    cx.set_state(indoc!(
15740        "fn a() {
15741             ˇdog();
15742
15743             cat();
15744        }"
15745    ));
15746    cx.update_editor(|editor, window, cx| {
15747        editor.toggle_comments(toggle_comments, window, cx);
15748    });
15749    cx.assert_editor_state(indoc!(
15750        "fn a() {
15751             // dog();
15752        ˇ
15753             cat();
15754        }"
15755    ));
15756
15757    // Single cursor on one line -> advance
15758    // Cursor starts and ends at column 0
15759    cx.set_state(indoc!(
15760        "fn a() {
15761         ˇ    dog();
15762             cat();
15763        }"
15764    ));
15765    cx.update_editor(|editor, window, cx| {
15766        editor.toggle_comments(toggle_comments, window, cx);
15767    });
15768    cx.assert_editor_state(indoc!(
15769        "fn a() {
15770             // dog();
15771         ˇ    cat();
15772        }"
15773    ));
15774}
15775
15776#[gpui::test]
15777async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15778    init_test(cx, |_| {});
15779
15780    let mut cx = EditorTestContext::new(cx).await;
15781
15782    let html_language = Arc::new(
15783        Language::new(
15784            LanguageConfig {
15785                name: "HTML".into(),
15786                block_comment: Some(BlockCommentConfig {
15787                    start: "<!-- ".into(),
15788                    prefix: "".into(),
15789                    end: " -->".into(),
15790                    tab_size: 0,
15791                }),
15792                ..Default::default()
15793            },
15794            Some(tree_sitter_html::LANGUAGE.into()),
15795        )
15796        .with_injection_query(
15797            r#"
15798            (script_element
15799                (raw_text) @injection.content
15800                (#set! injection.language "javascript"))
15801            "#,
15802        )
15803        .unwrap(),
15804    );
15805
15806    let javascript_language = Arc::new(Language::new(
15807        LanguageConfig {
15808            name: "JavaScript".into(),
15809            line_comments: vec!["// ".into()],
15810            ..Default::default()
15811        },
15812        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15813    ));
15814
15815    cx.language_registry().add(html_language.clone());
15816    cx.language_registry().add(javascript_language);
15817    cx.update_buffer(|buffer, cx| {
15818        buffer.set_language(Some(html_language), cx);
15819    });
15820
15821    // Toggle comments for empty selections
15822    cx.set_state(
15823        &r#"
15824            <p>A</p>ˇ
15825            <p>B</p>ˇ
15826            <p>C</p>ˇ
15827        "#
15828        .unindent(),
15829    );
15830    cx.update_editor(|editor, window, cx| {
15831        editor.toggle_comments(&ToggleComments::default(), window, cx)
15832    });
15833    cx.assert_editor_state(
15834        &r#"
15835            <!-- <p>A</p>ˇ -->
15836            <!-- <p>B</p>ˇ -->
15837            <!-- <p>C</p>ˇ -->
15838        "#
15839        .unindent(),
15840    );
15841    cx.update_editor(|editor, window, cx| {
15842        editor.toggle_comments(&ToggleComments::default(), window, cx)
15843    });
15844    cx.assert_editor_state(
15845        &r#"
15846            <p>A</p>ˇ
15847            <p>B</p>ˇ
15848            <p>C</p>ˇ
15849        "#
15850        .unindent(),
15851    );
15852
15853    // Toggle comments for mixture of empty and non-empty selections, where
15854    // multiple selections occupy a given line.
15855    cx.set_state(
15856        &r#"
15857            <p>A«</p>
15858            <p>ˇ»B</p>ˇ
15859            <p>C«</p>
15860            <p>ˇ»D</p>ˇ
15861        "#
15862        .unindent(),
15863    );
15864
15865    cx.update_editor(|editor, window, cx| {
15866        editor.toggle_comments(&ToggleComments::default(), window, cx)
15867    });
15868    cx.assert_editor_state(
15869        &r#"
15870            <!-- <p>A«</p>
15871            <p>ˇ»B</p>ˇ -->
15872            <!-- <p>C«</p>
15873            <p>ˇ»D</p>ˇ -->
15874        "#
15875        .unindent(),
15876    );
15877    cx.update_editor(|editor, window, cx| {
15878        editor.toggle_comments(&ToggleComments::default(), window, cx)
15879    });
15880    cx.assert_editor_state(
15881        &r#"
15882            <p>A«</p>
15883            <p>ˇ»B</p>ˇ
15884            <p>C«</p>
15885            <p>ˇ»D</p>ˇ
15886        "#
15887        .unindent(),
15888    );
15889
15890    // Toggle comments when different languages are active for different
15891    // selections.
15892    cx.set_state(
15893        &r#"
15894            ˇ<script>
15895                ˇvar x = new Y();
15896            ˇ</script>
15897        "#
15898        .unindent(),
15899    );
15900    cx.executor().run_until_parked();
15901    cx.update_editor(|editor, window, cx| {
15902        editor.toggle_comments(&ToggleComments::default(), window, cx)
15903    });
15904    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15905    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15906    cx.assert_editor_state(
15907        &r#"
15908            <!-- ˇ<script> -->
15909                // ˇvar x = new Y();
15910            <!-- ˇ</script> -->
15911        "#
15912        .unindent(),
15913    );
15914}
15915
15916#[gpui::test]
15917fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15918    init_test(cx, |_| {});
15919
15920    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15921    let multibuffer = cx.new(|cx| {
15922        let mut multibuffer = MultiBuffer::new(ReadWrite);
15923        multibuffer.push_excerpts(
15924            buffer.clone(),
15925            [
15926                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15927                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15928            ],
15929            cx,
15930        );
15931        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15932        multibuffer
15933    });
15934
15935    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15936    editor.update_in(cx, |editor, window, cx| {
15937        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15938        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15939            s.select_ranges([
15940                Point::new(0, 0)..Point::new(0, 0),
15941                Point::new(1, 0)..Point::new(1, 0),
15942            ])
15943        });
15944
15945        editor.handle_input("X", window, cx);
15946        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15947        assert_eq!(
15948            editor.selections.ranges(cx),
15949            [
15950                Point::new(0, 1)..Point::new(0, 1),
15951                Point::new(1, 1)..Point::new(1, 1),
15952            ]
15953        );
15954
15955        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15956        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15957            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15958        });
15959        editor.backspace(&Default::default(), window, cx);
15960        assert_eq!(editor.text(cx), "Xa\nbbb");
15961        assert_eq!(
15962            editor.selections.ranges(cx),
15963            [Point::new(1, 0)..Point::new(1, 0)]
15964        );
15965
15966        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15967            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15968        });
15969        editor.backspace(&Default::default(), window, cx);
15970        assert_eq!(editor.text(cx), "X\nbb");
15971        assert_eq!(
15972            editor.selections.ranges(cx),
15973            [Point::new(0, 1)..Point::new(0, 1)]
15974        );
15975    });
15976}
15977
15978#[gpui::test]
15979fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15980    init_test(cx, |_| {});
15981
15982    let markers = vec![('[', ']').into(), ('(', ')').into()];
15983    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15984        indoc! {"
15985            [aaaa
15986            (bbbb]
15987            cccc)",
15988        },
15989        markers.clone(),
15990    );
15991    let excerpt_ranges = markers.into_iter().map(|marker| {
15992        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15993        ExcerptRange::new(context)
15994    });
15995    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15996    let multibuffer = cx.new(|cx| {
15997        let mut multibuffer = MultiBuffer::new(ReadWrite);
15998        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15999        multibuffer
16000    });
16001
16002    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16003    editor.update_in(cx, |editor, window, cx| {
16004        let (expected_text, selection_ranges) = marked_text_ranges(
16005            indoc! {"
16006                aaaa
16007                bˇbbb
16008                bˇbbˇb
16009                cccc"
16010            },
16011            true,
16012        );
16013        assert_eq!(editor.text(cx), expected_text);
16014        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16015            s.select_ranges(selection_ranges)
16016        });
16017
16018        editor.handle_input("X", window, cx);
16019
16020        let (expected_text, expected_selections) = marked_text_ranges(
16021            indoc! {"
16022                aaaa
16023                bXˇbbXb
16024                bXˇbbXˇb
16025                cccc"
16026            },
16027            false,
16028        );
16029        assert_eq!(editor.text(cx), expected_text);
16030        assert_eq!(editor.selections.ranges(cx), expected_selections);
16031
16032        editor.newline(&Newline, window, cx);
16033        let (expected_text, expected_selections) = marked_text_ranges(
16034            indoc! {"
16035                aaaa
16036                bX
16037                ˇbbX
16038                b
16039                bX
16040                ˇbbX
16041                ˇb
16042                cccc"
16043            },
16044            false,
16045        );
16046        assert_eq!(editor.text(cx), expected_text);
16047        assert_eq!(editor.selections.ranges(cx), expected_selections);
16048    });
16049}
16050
16051#[gpui::test]
16052fn test_refresh_selections(cx: &mut TestAppContext) {
16053    init_test(cx, |_| {});
16054
16055    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16056    let mut excerpt1_id = None;
16057    let multibuffer = cx.new(|cx| {
16058        let mut multibuffer = MultiBuffer::new(ReadWrite);
16059        excerpt1_id = multibuffer
16060            .push_excerpts(
16061                buffer.clone(),
16062                [
16063                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16064                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16065                ],
16066                cx,
16067            )
16068            .into_iter()
16069            .next();
16070        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16071        multibuffer
16072    });
16073
16074    let editor = cx.add_window(|window, cx| {
16075        let mut editor = build_editor(multibuffer.clone(), window, cx);
16076        let snapshot = editor.snapshot(window, cx);
16077        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16078            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16079        });
16080        editor.begin_selection(
16081            Point::new(2, 1).to_display_point(&snapshot),
16082            true,
16083            1,
16084            window,
16085            cx,
16086        );
16087        assert_eq!(
16088            editor.selections.ranges(cx),
16089            [
16090                Point::new(1, 3)..Point::new(1, 3),
16091                Point::new(2, 1)..Point::new(2, 1),
16092            ]
16093        );
16094        editor
16095    });
16096
16097    // Refreshing selections is a no-op when excerpts haven't changed.
16098    _ = editor.update(cx, |editor, window, cx| {
16099        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16100        assert_eq!(
16101            editor.selections.ranges(cx),
16102            [
16103                Point::new(1, 3)..Point::new(1, 3),
16104                Point::new(2, 1)..Point::new(2, 1),
16105            ]
16106        );
16107    });
16108
16109    multibuffer.update(cx, |multibuffer, cx| {
16110        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16111    });
16112    _ = editor.update(cx, |editor, window, cx| {
16113        // Removing an excerpt causes the first selection to become degenerate.
16114        assert_eq!(
16115            editor.selections.ranges(cx),
16116            [
16117                Point::new(0, 0)..Point::new(0, 0),
16118                Point::new(0, 1)..Point::new(0, 1)
16119            ]
16120        );
16121
16122        // Refreshing selections will relocate the first selection to the original buffer
16123        // location.
16124        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16125        assert_eq!(
16126            editor.selections.ranges(cx),
16127            [
16128                Point::new(0, 1)..Point::new(0, 1),
16129                Point::new(0, 3)..Point::new(0, 3)
16130            ]
16131        );
16132        assert!(editor.selections.pending_anchor().is_some());
16133    });
16134}
16135
16136#[gpui::test]
16137fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16138    init_test(cx, |_| {});
16139
16140    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16141    let mut excerpt1_id = None;
16142    let multibuffer = cx.new(|cx| {
16143        let mut multibuffer = MultiBuffer::new(ReadWrite);
16144        excerpt1_id = multibuffer
16145            .push_excerpts(
16146                buffer.clone(),
16147                [
16148                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16149                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16150                ],
16151                cx,
16152            )
16153            .into_iter()
16154            .next();
16155        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16156        multibuffer
16157    });
16158
16159    let editor = cx.add_window(|window, cx| {
16160        let mut editor = build_editor(multibuffer.clone(), window, cx);
16161        let snapshot = editor.snapshot(window, cx);
16162        editor.begin_selection(
16163            Point::new(1, 3).to_display_point(&snapshot),
16164            false,
16165            1,
16166            window,
16167            cx,
16168        );
16169        assert_eq!(
16170            editor.selections.ranges(cx),
16171            [Point::new(1, 3)..Point::new(1, 3)]
16172        );
16173        editor
16174    });
16175
16176    multibuffer.update(cx, |multibuffer, cx| {
16177        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16178    });
16179    _ = editor.update(cx, |editor, window, cx| {
16180        assert_eq!(
16181            editor.selections.ranges(cx),
16182            [Point::new(0, 0)..Point::new(0, 0)]
16183        );
16184
16185        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16186        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16187        assert_eq!(
16188            editor.selections.ranges(cx),
16189            [Point::new(0, 3)..Point::new(0, 3)]
16190        );
16191        assert!(editor.selections.pending_anchor().is_some());
16192    });
16193}
16194
16195#[gpui::test]
16196async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16197    init_test(cx, |_| {});
16198
16199    let language = Arc::new(
16200        Language::new(
16201            LanguageConfig {
16202                brackets: BracketPairConfig {
16203                    pairs: vec![
16204                        BracketPair {
16205                            start: "{".to_string(),
16206                            end: "}".to_string(),
16207                            close: true,
16208                            surround: true,
16209                            newline: true,
16210                        },
16211                        BracketPair {
16212                            start: "/* ".to_string(),
16213                            end: " */".to_string(),
16214                            close: true,
16215                            surround: true,
16216                            newline: true,
16217                        },
16218                    ],
16219                    ..Default::default()
16220                },
16221                ..Default::default()
16222            },
16223            Some(tree_sitter_rust::LANGUAGE.into()),
16224        )
16225        .with_indents_query("")
16226        .unwrap(),
16227    );
16228
16229    let text = concat!(
16230        "{   }\n",     //
16231        "  x\n",       //
16232        "  /*   */\n", //
16233        "x\n",         //
16234        "{{} }\n",     //
16235    );
16236
16237    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16238    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16239    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16240    editor
16241        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16242        .await;
16243
16244    editor.update_in(cx, |editor, window, cx| {
16245        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16246            s.select_display_ranges([
16247                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16248                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16249                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16250            ])
16251        });
16252        editor.newline(&Newline, window, cx);
16253
16254        assert_eq!(
16255            editor.buffer().read(cx).read(cx).text(),
16256            concat!(
16257                "{ \n",    // Suppress rustfmt
16258                "\n",      //
16259                "}\n",     //
16260                "  x\n",   //
16261                "  /* \n", //
16262                "  \n",    //
16263                "  */\n",  //
16264                "x\n",     //
16265                "{{} \n",  //
16266                "}\n",     //
16267            )
16268        );
16269    });
16270}
16271
16272#[gpui::test]
16273fn test_highlighted_ranges(cx: &mut TestAppContext) {
16274    init_test(cx, |_| {});
16275
16276    let editor = cx.add_window(|window, cx| {
16277        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16278        build_editor(buffer, window, cx)
16279    });
16280
16281    _ = editor.update(cx, |editor, window, cx| {
16282        struct Type1;
16283        struct Type2;
16284
16285        let buffer = editor.buffer.read(cx).snapshot(cx);
16286
16287        let anchor_range =
16288            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16289
16290        editor.highlight_background::<Type1>(
16291            &[
16292                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16293                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16294                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16295                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16296            ],
16297            |_| Hsla::red(),
16298            cx,
16299        );
16300        editor.highlight_background::<Type2>(
16301            &[
16302                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16303                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16304                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16305                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16306            ],
16307            |_| Hsla::green(),
16308            cx,
16309        );
16310
16311        let snapshot = editor.snapshot(window, cx);
16312        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16313            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16314            &snapshot,
16315            cx.theme(),
16316        );
16317        assert_eq!(
16318            highlighted_ranges,
16319            &[
16320                (
16321                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16322                    Hsla::green(),
16323                ),
16324                (
16325                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16326                    Hsla::red(),
16327                ),
16328                (
16329                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16330                    Hsla::green(),
16331                ),
16332                (
16333                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16334                    Hsla::red(),
16335                ),
16336            ]
16337        );
16338        assert_eq!(
16339            editor.sorted_background_highlights_in_range(
16340                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16341                &snapshot,
16342                cx.theme(),
16343            ),
16344            &[(
16345                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16346                Hsla::red(),
16347            )]
16348        );
16349    });
16350}
16351
16352#[gpui::test]
16353async fn test_following(cx: &mut TestAppContext) {
16354    init_test(cx, |_| {});
16355
16356    let fs = FakeFs::new(cx.executor());
16357    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16358
16359    let buffer = project.update(cx, |project, cx| {
16360        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16361        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16362    });
16363    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16364    let follower = cx.update(|cx| {
16365        cx.open_window(
16366            WindowOptions {
16367                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16368                    gpui::Point::new(px(0.), px(0.)),
16369                    gpui::Point::new(px(10.), px(80.)),
16370                ))),
16371                ..Default::default()
16372            },
16373            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16374        )
16375        .unwrap()
16376    });
16377
16378    let is_still_following = Rc::new(RefCell::new(true));
16379    let follower_edit_event_count = Rc::new(RefCell::new(0));
16380    let pending_update = Rc::new(RefCell::new(None));
16381    let leader_entity = leader.root(cx).unwrap();
16382    let follower_entity = follower.root(cx).unwrap();
16383    _ = follower.update(cx, {
16384        let update = pending_update.clone();
16385        let is_still_following = is_still_following.clone();
16386        let follower_edit_event_count = follower_edit_event_count.clone();
16387        |_, window, cx| {
16388            cx.subscribe_in(
16389                &leader_entity,
16390                window,
16391                move |_, leader, event, window, cx| {
16392                    leader.read(cx).add_event_to_update_proto(
16393                        event,
16394                        &mut update.borrow_mut(),
16395                        window,
16396                        cx,
16397                    );
16398                },
16399            )
16400            .detach();
16401
16402            cx.subscribe_in(
16403                &follower_entity,
16404                window,
16405                move |_, _, event: &EditorEvent, _window, _cx| {
16406                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16407                        *is_still_following.borrow_mut() = false;
16408                    }
16409
16410                    if let EditorEvent::BufferEdited = event {
16411                        *follower_edit_event_count.borrow_mut() += 1;
16412                    }
16413                },
16414            )
16415            .detach();
16416        }
16417    });
16418
16419    // Update the selections only
16420    _ = leader.update(cx, |leader, window, cx| {
16421        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16422            s.select_ranges([1..1])
16423        });
16424    });
16425    follower
16426        .update(cx, |follower, window, cx| {
16427            follower.apply_update_proto(
16428                &project,
16429                pending_update.borrow_mut().take().unwrap(),
16430                window,
16431                cx,
16432            )
16433        })
16434        .unwrap()
16435        .await
16436        .unwrap();
16437    _ = follower.update(cx, |follower, _, cx| {
16438        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
16439    });
16440    assert!(*is_still_following.borrow());
16441    assert_eq!(*follower_edit_event_count.borrow(), 0);
16442
16443    // Update the scroll position only
16444    _ = leader.update(cx, |leader, window, cx| {
16445        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16446    });
16447    follower
16448        .update(cx, |follower, window, cx| {
16449            follower.apply_update_proto(
16450                &project,
16451                pending_update.borrow_mut().take().unwrap(),
16452                window,
16453                cx,
16454            )
16455        })
16456        .unwrap()
16457        .await
16458        .unwrap();
16459    assert_eq!(
16460        follower
16461            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16462            .unwrap(),
16463        gpui::Point::new(1.5, 3.5)
16464    );
16465    assert!(*is_still_following.borrow());
16466    assert_eq!(*follower_edit_event_count.borrow(), 0);
16467
16468    // Update the selections and scroll position. The follower's scroll position is updated
16469    // via autoscroll, not via the leader's exact scroll position.
16470    _ = leader.update(cx, |leader, window, cx| {
16471        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16472            s.select_ranges([0..0])
16473        });
16474        leader.request_autoscroll(Autoscroll::newest(), cx);
16475        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16476    });
16477    follower
16478        .update(cx, |follower, window, cx| {
16479            follower.apply_update_proto(
16480                &project,
16481                pending_update.borrow_mut().take().unwrap(),
16482                window,
16483                cx,
16484            )
16485        })
16486        .unwrap()
16487        .await
16488        .unwrap();
16489    _ = follower.update(cx, |follower, _, cx| {
16490        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16491        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16492    });
16493    assert!(*is_still_following.borrow());
16494
16495    // Creating a pending selection that precedes another selection
16496    _ = leader.update(cx, |leader, window, cx| {
16497        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16498            s.select_ranges([1..1])
16499        });
16500        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16501    });
16502    follower
16503        .update(cx, |follower, window, cx| {
16504            follower.apply_update_proto(
16505                &project,
16506                pending_update.borrow_mut().take().unwrap(),
16507                window,
16508                cx,
16509            )
16510        })
16511        .unwrap()
16512        .await
16513        .unwrap();
16514    _ = follower.update(cx, |follower, _, cx| {
16515        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16516    });
16517    assert!(*is_still_following.borrow());
16518
16519    // Extend the pending selection so that it surrounds another selection
16520    _ = leader.update(cx, |leader, window, cx| {
16521        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16522    });
16523    follower
16524        .update(cx, |follower, window, cx| {
16525            follower.apply_update_proto(
16526                &project,
16527                pending_update.borrow_mut().take().unwrap(),
16528                window,
16529                cx,
16530            )
16531        })
16532        .unwrap()
16533        .await
16534        .unwrap();
16535    _ = follower.update(cx, |follower, _, cx| {
16536        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16537    });
16538
16539    // Scrolling locally breaks the follow
16540    _ = follower.update(cx, |follower, window, cx| {
16541        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16542        follower.set_scroll_anchor(
16543            ScrollAnchor {
16544                anchor: top_anchor,
16545                offset: gpui::Point::new(0.0, 0.5),
16546            },
16547            window,
16548            cx,
16549        );
16550    });
16551    assert!(!(*is_still_following.borrow()));
16552}
16553
16554#[gpui::test]
16555async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16556    init_test(cx, |_| {});
16557
16558    let fs = FakeFs::new(cx.executor());
16559    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16560    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16561    let pane = workspace
16562        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16563        .unwrap();
16564
16565    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16566
16567    let leader = pane.update_in(cx, |_, window, cx| {
16568        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16569        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16570    });
16571
16572    // Start following the editor when it has no excerpts.
16573    let mut state_message =
16574        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16575    let workspace_entity = workspace.root(cx).unwrap();
16576    let follower_1 = cx
16577        .update_window(*workspace.deref(), |_, window, cx| {
16578            Editor::from_state_proto(
16579                workspace_entity,
16580                ViewId {
16581                    creator: CollaboratorId::PeerId(PeerId::default()),
16582                    id: 0,
16583                },
16584                &mut state_message,
16585                window,
16586                cx,
16587            )
16588        })
16589        .unwrap()
16590        .unwrap()
16591        .await
16592        .unwrap();
16593
16594    let update_message = Rc::new(RefCell::new(None));
16595    follower_1.update_in(cx, {
16596        let update = update_message.clone();
16597        |_, window, cx| {
16598            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16599                leader.read(cx).add_event_to_update_proto(
16600                    event,
16601                    &mut update.borrow_mut(),
16602                    window,
16603                    cx,
16604                );
16605            })
16606            .detach();
16607        }
16608    });
16609
16610    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16611        (
16612            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16613            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16614        )
16615    });
16616
16617    // Insert some excerpts.
16618    leader.update(cx, |leader, cx| {
16619        leader.buffer.update(cx, |multibuffer, cx| {
16620            multibuffer.set_excerpts_for_path(
16621                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
16622                buffer_1.clone(),
16623                vec![
16624                    Point::row_range(0..3),
16625                    Point::row_range(1..6),
16626                    Point::row_range(12..15),
16627                ],
16628                0,
16629                cx,
16630            );
16631            multibuffer.set_excerpts_for_path(
16632                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
16633                buffer_2.clone(),
16634                vec![Point::row_range(0..6), Point::row_range(8..12)],
16635                0,
16636                cx,
16637            );
16638        });
16639    });
16640
16641    // Apply the update of adding the excerpts.
16642    follower_1
16643        .update_in(cx, |follower, window, cx| {
16644            follower.apply_update_proto(
16645                &project,
16646                update_message.borrow().clone().unwrap(),
16647                window,
16648                cx,
16649            )
16650        })
16651        .await
16652        .unwrap();
16653    assert_eq!(
16654        follower_1.update(cx, |editor, cx| editor.text(cx)),
16655        leader.update(cx, |editor, cx| editor.text(cx))
16656    );
16657    update_message.borrow_mut().take();
16658
16659    // Start following separately after it already has excerpts.
16660    let mut state_message =
16661        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16662    let workspace_entity = workspace.root(cx).unwrap();
16663    let follower_2 = cx
16664        .update_window(*workspace.deref(), |_, window, cx| {
16665            Editor::from_state_proto(
16666                workspace_entity,
16667                ViewId {
16668                    creator: CollaboratorId::PeerId(PeerId::default()),
16669                    id: 0,
16670                },
16671                &mut state_message,
16672                window,
16673                cx,
16674            )
16675        })
16676        .unwrap()
16677        .unwrap()
16678        .await
16679        .unwrap();
16680    assert_eq!(
16681        follower_2.update(cx, |editor, cx| editor.text(cx)),
16682        leader.update(cx, |editor, cx| editor.text(cx))
16683    );
16684
16685    // Remove some excerpts.
16686    leader.update(cx, |leader, cx| {
16687        leader.buffer.update(cx, |multibuffer, cx| {
16688            let excerpt_ids = multibuffer.excerpt_ids();
16689            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16690            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16691        });
16692    });
16693
16694    // Apply the update of removing the excerpts.
16695    follower_1
16696        .update_in(cx, |follower, window, cx| {
16697            follower.apply_update_proto(
16698                &project,
16699                update_message.borrow().clone().unwrap(),
16700                window,
16701                cx,
16702            )
16703        })
16704        .await
16705        .unwrap();
16706    follower_2
16707        .update_in(cx, |follower, window, cx| {
16708            follower.apply_update_proto(
16709                &project,
16710                update_message.borrow().clone().unwrap(),
16711                window,
16712                cx,
16713            )
16714        })
16715        .await
16716        .unwrap();
16717    update_message.borrow_mut().take();
16718    assert_eq!(
16719        follower_1.update(cx, |editor, cx| editor.text(cx)),
16720        leader.update(cx, |editor, cx| editor.text(cx))
16721    );
16722}
16723
16724#[gpui::test]
16725async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16726    init_test(cx, |_| {});
16727
16728    let mut cx = EditorTestContext::new(cx).await;
16729    let lsp_store =
16730        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16731
16732    cx.set_state(indoc! {"
16733        ˇfn func(abc def: i32) -> u32 {
16734        }
16735    "});
16736
16737    cx.update(|_, cx| {
16738        lsp_store.update(cx, |lsp_store, cx| {
16739            lsp_store
16740                .update_diagnostics(
16741                    LanguageServerId(0),
16742                    lsp::PublishDiagnosticsParams {
16743                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16744                        version: None,
16745                        diagnostics: vec![
16746                            lsp::Diagnostic {
16747                                range: lsp::Range::new(
16748                                    lsp::Position::new(0, 11),
16749                                    lsp::Position::new(0, 12),
16750                                ),
16751                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16752                                ..Default::default()
16753                            },
16754                            lsp::Diagnostic {
16755                                range: lsp::Range::new(
16756                                    lsp::Position::new(0, 12),
16757                                    lsp::Position::new(0, 15),
16758                                ),
16759                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16760                                ..Default::default()
16761                            },
16762                            lsp::Diagnostic {
16763                                range: lsp::Range::new(
16764                                    lsp::Position::new(0, 25),
16765                                    lsp::Position::new(0, 28),
16766                                ),
16767                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16768                                ..Default::default()
16769                            },
16770                        ],
16771                    },
16772                    None,
16773                    DiagnosticSourceKind::Pushed,
16774                    &[],
16775                    cx,
16776                )
16777                .unwrap()
16778        });
16779    });
16780
16781    executor.run_until_parked();
16782
16783    cx.update_editor(|editor, window, cx| {
16784        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16785    });
16786
16787    cx.assert_editor_state(indoc! {"
16788        fn func(abc def: i32) -> ˇu32 {
16789        }
16790    "});
16791
16792    cx.update_editor(|editor, window, cx| {
16793        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16794    });
16795
16796    cx.assert_editor_state(indoc! {"
16797        fn func(abc ˇdef: i32) -> u32 {
16798        }
16799    "});
16800
16801    cx.update_editor(|editor, window, cx| {
16802        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16803    });
16804
16805    cx.assert_editor_state(indoc! {"
16806        fn func(abcˇ def: i32) -> u32 {
16807        }
16808    "});
16809
16810    cx.update_editor(|editor, window, cx| {
16811        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16812    });
16813
16814    cx.assert_editor_state(indoc! {"
16815        fn func(abc def: i32) -> ˇu32 {
16816        }
16817    "});
16818}
16819
16820#[gpui::test]
16821async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16822    init_test(cx, |_| {});
16823
16824    let mut cx = EditorTestContext::new(cx).await;
16825
16826    let diff_base = r#"
16827        use some::mod;
16828
16829        const A: u32 = 42;
16830
16831        fn main() {
16832            println!("hello");
16833
16834            println!("world");
16835        }
16836        "#
16837    .unindent();
16838
16839    // Edits are modified, removed, modified, added
16840    cx.set_state(
16841        &r#"
16842        use some::modified;
16843
16844        ˇ
16845        fn main() {
16846            println!("hello there");
16847
16848            println!("around the");
16849            println!("world");
16850        }
16851        "#
16852        .unindent(),
16853    );
16854
16855    cx.set_head_text(&diff_base);
16856    executor.run_until_parked();
16857
16858    cx.update_editor(|editor, window, cx| {
16859        //Wrap around the bottom of the buffer
16860        for _ in 0..3 {
16861            editor.go_to_next_hunk(&GoToHunk, window, cx);
16862        }
16863    });
16864
16865    cx.assert_editor_state(
16866        &r#"
16867        ˇuse some::modified;
16868
16869
16870        fn main() {
16871            println!("hello there");
16872
16873            println!("around the");
16874            println!("world");
16875        }
16876        "#
16877        .unindent(),
16878    );
16879
16880    cx.update_editor(|editor, window, cx| {
16881        //Wrap around the top of the buffer
16882        for _ in 0..2 {
16883            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16884        }
16885    });
16886
16887    cx.assert_editor_state(
16888        &r#"
16889        use some::modified;
16890
16891
16892        fn main() {
16893        ˇ    println!("hello there");
16894
16895            println!("around the");
16896            println!("world");
16897        }
16898        "#
16899        .unindent(),
16900    );
16901
16902    cx.update_editor(|editor, window, cx| {
16903        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16904    });
16905
16906    cx.assert_editor_state(
16907        &r#"
16908        use some::modified;
16909
16910        ˇ
16911        fn main() {
16912            println!("hello there");
16913
16914            println!("around the");
16915            println!("world");
16916        }
16917        "#
16918        .unindent(),
16919    );
16920
16921    cx.update_editor(|editor, window, cx| {
16922        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16923    });
16924
16925    cx.assert_editor_state(
16926        &r#"
16927        ˇuse some::modified;
16928
16929
16930        fn main() {
16931            println!("hello there");
16932
16933            println!("around the");
16934            println!("world");
16935        }
16936        "#
16937        .unindent(),
16938    );
16939
16940    cx.update_editor(|editor, window, cx| {
16941        for _ in 0..2 {
16942            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16943        }
16944    });
16945
16946    cx.assert_editor_state(
16947        &r#"
16948        use some::modified;
16949
16950
16951        fn main() {
16952        ˇ    println!("hello there");
16953
16954            println!("around the");
16955            println!("world");
16956        }
16957        "#
16958        .unindent(),
16959    );
16960
16961    cx.update_editor(|editor, window, cx| {
16962        editor.fold(&Fold, window, cx);
16963    });
16964
16965    cx.update_editor(|editor, window, cx| {
16966        editor.go_to_next_hunk(&GoToHunk, window, cx);
16967    });
16968
16969    cx.assert_editor_state(
16970        &r#"
16971        ˇuse some::modified;
16972
16973
16974        fn main() {
16975            println!("hello there");
16976
16977            println!("around the");
16978            println!("world");
16979        }
16980        "#
16981        .unindent(),
16982    );
16983}
16984
16985#[test]
16986fn test_split_words() {
16987    fn split(text: &str) -> Vec<&str> {
16988        split_words(text).collect()
16989    }
16990
16991    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16992    assert_eq!(split("hello_world"), &["hello_", "world"]);
16993    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16994    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16995    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16996    assert_eq!(split("helloworld"), &["helloworld"]);
16997
16998    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16999}
17000
17001#[gpui::test]
17002async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17003    init_test(cx, |_| {});
17004
17005    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17006    let mut assert = |before, after| {
17007        let _state_context = cx.set_state(before);
17008        cx.run_until_parked();
17009        cx.update_editor(|editor, window, cx| {
17010            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17011        });
17012        cx.run_until_parked();
17013        cx.assert_editor_state(after);
17014    };
17015
17016    // Outside bracket jumps to outside of matching bracket
17017    assert("console.logˇ(var);", "console.log(var)ˇ;");
17018    assert("console.log(var)ˇ;", "console.logˇ(var);");
17019
17020    // Inside bracket jumps to inside of matching bracket
17021    assert("console.log(ˇvar);", "console.log(varˇ);");
17022    assert("console.log(varˇ);", "console.log(ˇvar);");
17023
17024    // When outside a bracket and inside, favor jumping to the inside bracket
17025    assert(
17026        "console.log('foo', [1, 2, 3]ˇ);",
17027        "console.log(ˇ'foo', [1, 2, 3]);",
17028    );
17029    assert(
17030        "console.log(ˇ'foo', [1, 2, 3]);",
17031        "console.log('foo', [1, 2, 3]ˇ);",
17032    );
17033
17034    // Bias forward if two options are equally likely
17035    assert(
17036        "let result = curried_fun()ˇ();",
17037        "let result = curried_fun()()ˇ;",
17038    );
17039
17040    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17041    assert(
17042        indoc! {"
17043            function test() {
17044                console.log('test')ˇ
17045            }"},
17046        indoc! {"
17047            function test() {
17048                console.logˇ('test')
17049            }"},
17050    );
17051}
17052
17053#[gpui::test]
17054async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17055    init_test(cx, |_| {});
17056
17057    let fs = FakeFs::new(cx.executor());
17058    fs.insert_tree(
17059        path!("/a"),
17060        json!({
17061            "main.rs": "fn main() { let a = 5; }",
17062            "other.rs": "// Test file",
17063        }),
17064    )
17065    .await;
17066    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17067
17068    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17069    language_registry.add(Arc::new(Language::new(
17070        LanguageConfig {
17071            name: "Rust".into(),
17072            matcher: LanguageMatcher {
17073                path_suffixes: vec!["rs".to_string()],
17074                ..Default::default()
17075            },
17076            brackets: BracketPairConfig {
17077                pairs: vec![BracketPair {
17078                    start: "{".to_string(),
17079                    end: "}".to_string(),
17080                    close: true,
17081                    surround: true,
17082                    newline: true,
17083                }],
17084                disabled_scopes_by_bracket_ix: Vec::new(),
17085            },
17086            ..Default::default()
17087        },
17088        Some(tree_sitter_rust::LANGUAGE.into()),
17089    )));
17090    let mut fake_servers = language_registry.register_fake_lsp(
17091        "Rust",
17092        FakeLspAdapter {
17093            capabilities: lsp::ServerCapabilities {
17094                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17095                    first_trigger_character: "{".to_string(),
17096                    more_trigger_character: None,
17097                }),
17098                ..Default::default()
17099            },
17100            ..Default::default()
17101        },
17102    );
17103
17104    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17105
17106    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17107
17108    let worktree_id = workspace
17109        .update(cx, |workspace, _, cx| {
17110            workspace.project().update(cx, |project, cx| {
17111                project.worktrees(cx).next().unwrap().read(cx).id()
17112            })
17113        })
17114        .unwrap();
17115
17116    let buffer = project
17117        .update(cx, |project, cx| {
17118            project.open_local_buffer(path!("/a/main.rs"), cx)
17119        })
17120        .await
17121        .unwrap();
17122    let editor_handle = workspace
17123        .update(cx, |workspace, window, cx| {
17124            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17125        })
17126        .unwrap()
17127        .await
17128        .unwrap()
17129        .downcast::<Editor>()
17130        .unwrap();
17131
17132    cx.executor().start_waiting();
17133    let fake_server = fake_servers.next().await.unwrap();
17134
17135    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17136        |params, _| async move {
17137            assert_eq!(
17138                params.text_document_position.text_document.uri,
17139                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17140            );
17141            assert_eq!(
17142                params.text_document_position.position,
17143                lsp::Position::new(0, 21),
17144            );
17145
17146            Ok(Some(vec![lsp::TextEdit {
17147                new_text: "]".to_string(),
17148                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17149            }]))
17150        },
17151    );
17152
17153    editor_handle.update_in(cx, |editor, window, cx| {
17154        window.focus(&editor.focus_handle(cx));
17155        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17156            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17157        });
17158        editor.handle_input("{", window, cx);
17159    });
17160
17161    cx.executor().run_until_parked();
17162
17163    buffer.update(cx, |buffer, _| {
17164        assert_eq!(
17165            buffer.text(),
17166            "fn main() { let a = {5}; }",
17167            "No extra braces from on type formatting should appear in the buffer"
17168        )
17169    });
17170}
17171
17172#[gpui::test(iterations = 20, seeds(31))]
17173async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17174    init_test(cx, |_| {});
17175
17176    let mut cx = EditorLspTestContext::new_rust(
17177        lsp::ServerCapabilities {
17178            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17179                first_trigger_character: ".".to_string(),
17180                more_trigger_character: None,
17181            }),
17182            ..Default::default()
17183        },
17184        cx,
17185    )
17186    .await;
17187
17188    cx.update_buffer(|buffer, _| {
17189        // This causes autoindent to be async.
17190        buffer.set_sync_parse_timeout(Duration::ZERO)
17191    });
17192
17193    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17194    cx.simulate_keystroke("\n");
17195    cx.run_until_parked();
17196
17197    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17198    let mut request =
17199        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17200            let buffer_cloned = buffer_cloned.clone();
17201            async move {
17202                buffer_cloned.update(&mut cx, |buffer, _| {
17203                    assert_eq!(
17204                        buffer.text(),
17205                        "fn c() {\n    d()\n        .\n}\n",
17206                        "OnTypeFormatting should triggered after autoindent applied"
17207                    )
17208                })?;
17209
17210                Ok(Some(vec![]))
17211            }
17212        });
17213
17214    cx.simulate_keystroke(".");
17215    cx.run_until_parked();
17216
17217    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17218    assert!(request.next().await.is_some());
17219    request.close();
17220    assert!(request.next().await.is_none());
17221}
17222
17223#[gpui::test]
17224async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17225    init_test(cx, |_| {});
17226
17227    let fs = FakeFs::new(cx.executor());
17228    fs.insert_tree(
17229        path!("/a"),
17230        json!({
17231            "main.rs": "fn main() { let a = 5; }",
17232            "other.rs": "// Test file",
17233        }),
17234    )
17235    .await;
17236
17237    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17238
17239    let server_restarts = Arc::new(AtomicUsize::new(0));
17240    let closure_restarts = Arc::clone(&server_restarts);
17241    let language_server_name = "test language server";
17242    let language_name: LanguageName = "Rust".into();
17243
17244    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17245    language_registry.add(Arc::new(Language::new(
17246        LanguageConfig {
17247            name: language_name.clone(),
17248            matcher: LanguageMatcher {
17249                path_suffixes: vec!["rs".to_string()],
17250                ..Default::default()
17251            },
17252            ..Default::default()
17253        },
17254        Some(tree_sitter_rust::LANGUAGE.into()),
17255    )));
17256    let mut fake_servers = language_registry.register_fake_lsp(
17257        "Rust",
17258        FakeLspAdapter {
17259            name: language_server_name,
17260            initialization_options: Some(json!({
17261                "testOptionValue": true
17262            })),
17263            initializer: Some(Box::new(move |fake_server| {
17264                let task_restarts = Arc::clone(&closure_restarts);
17265                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17266                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17267                    futures::future::ready(Ok(()))
17268                });
17269            })),
17270            ..Default::default()
17271        },
17272    );
17273
17274    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17275    let _buffer = project
17276        .update(cx, |project, cx| {
17277            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17278        })
17279        .await
17280        .unwrap();
17281    let _fake_server = fake_servers.next().await.unwrap();
17282    update_test_language_settings(cx, |language_settings| {
17283        language_settings.languages.0.insert(
17284            language_name.clone().0,
17285            LanguageSettingsContent {
17286                tab_size: NonZeroU32::new(8),
17287                ..Default::default()
17288            },
17289        );
17290    });
17291    cx.executor().run_until_parked();
17292    assert_eq!(
17293        server_restarts.load(atomic::Ordering::Acquire),
17294        0,
17295        "Should not restart LSP server on an unrelated change"
17296    );
17297
17298    update_test_project_settings(cx, |project_settings| {
17299        project_settings.lsp.insert(
17300            "Some other server name".into(),
17301            LspSettings {
17302                binary: None,
17303                settings: None,
17304                initialization_options: Some(json!({
17305                    "some other init value": false
17306                })),
17307                enable_lsp_tasks: false,
17308                fetch: None,
17309            },
17310        );
17311    });
17312    cx.executor().run_until_parked();
17313    assert_eq!(
17314        server_restarts.load(atomic::Ordering::Acquire),
17315        0,
17316        "Should not restart LSP server on an unrelated LSP settings change"
17317    );
17318
17319    update_test_project_settings(cx, |project_settings| {
17320        project_settings.lsp.insert(
17321            language_server_name.into(),
17322            LspSettings {
17323                binary: None,
17324                settings: None,
17325                initialization_options: Some(json!({
17326                    "anotherInitValue": false
17327                })),
17328                enable_lsp_tasks: false,
17329                fetch: None,
17330            },
17331        );
17332    });
17333    cx.executor().run_until_parked();
17334    assert_eq!(
17335        server_restarts.load(atomic::Ordering::Acquire),
17336        1,
17337        "Should restart LSP server on a related LSP settings change"
17338    );
17339
17340    update_test_project_settings(cx, |project_settings| {
17341        project_settings.lsp.insert(
17342            language_server_name.into(),
17343            LspSettings {
17344                binary: None,
17345                settings: None,
17346                initialization_options: Some(json!({
17347                    "anotherInitValue": false
17348                })),
17349                enable_lsp_tasks: false,
17350                fetch: None,
17351            },
17352        );
17353    });
17354    cx.executor().run_until_parked();
17355    assert_eq!(
17356        server_restarts.load(atomic::Ordering::Acquire),
17357        1,
17358        "Should not restart LSP server on a related LSP settings change that is the same"
17359    );
17360
17361    update_test_project_settings(cx, |project_settings| {
17362        project_settings.lsp.insert(
17363            language_server_name.into(),
17364            LspSettings {
17365                binary: None,
17366                settings: None,
17367                initialization_options: None,
17368                enable_lsp_tasks: false,
17369                fetch: None,
17370            },
17371        );
17372    });
17373    cx.executor().run_until_parked();
17374    assert_eq!(
17375        server_restarts.load(atomic::Ordering::Acquire),
17376        2,
17377        "Should restart LSP server on another related LSP settings change"
17378    );
17379}
17380
17381#[gpui::test]
17382async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17383    init_test(cx, |_| {});
17384
17385    let mut cx = EditorLspTestContext::new_rust(
17386        lsp::ServerCapabilities {
17387            completion_provider: Some(lsp::CompletionOptions {
17388                trigger_characters: Some(vec![".".to_string()]),
17389                resolve_provider: Some(true),
17390                ..Default::default()
17391            }),
17392            ..Default::default()
17393        },
17394        cx,
17395    )
17396    .await;
17397
17398    cx.set_state("fn main() { let a = 2ˇ; }");
17399    cx.simulate_keystroke(".");
17400    let completion_item = lsp::CompletionItem {
17401        label: "some".into(),
17402        kind: Some(lsp::CompletionItemKind::SNIPPET),
17403        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17404        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17405            kind: lsp::MarkupKind::Markdown,
17406            value: "```rust\nSome(2)\n```".to_string(),
17407        })),
17408        deprecated: Some(false),
17409        sort_text: Some("fffffff2".to_string()),
17410        filter_text: Some("some".to_string()),
17411        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17412        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17413            range: lsp::Range {
17414                start: lsp::Position {
17415                    line: 0,
17416                    character: 22,
17417                },
17418                end: lsp::Position {
17419                    line: 0,
17420                    character: 22,
17421                },
17422            },
17423            new_text: "Some(2)".to_string(),
17424        })),
17425        additional_text_edits: Some(vec![lsp::TextEdit {
17426            range: lsp::Range {
17427                start: lsp::Position {
17428                    line: 0,
17429                    character: 20,
17430                },
17431                end: lsp::Position {
17432                    line: 0,
17433                    character: 22,
17434                },
17435            },
17436            new_text: "".to_string(),
17437        }]),
17438        ..Default::default()
17439    };
17440
17441    let closure_completion_item = completion_item.clone();
17442    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17443        let task_completion_item = closure_completion_item.clone();
17444        async move {
17445            Ok(Some(lsp::CompletionResponse::Array(vec![
17446                task_completion_item,
17447            ])))
17448        }
17449    });
17450
17451    request.next().await;
17452
17453    cx.condition(|editor, _| editor.context_menu_visible())
17454        .await;
17455    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17456        editor
17457            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17458            .unwrap()
17459    });
17460    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17461
17462    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17463        let task_completion_item = completion_item.clone();
17464        async move { Ok(task_completion_item) }
17465    })
17466    .next()
17467    .await
17468    .unwrap();
17469    apply_additional_edits.await.unwrap();
17470    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17471}
17472
17473#[gpui::test]
17474async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17475    init_test(cx, |_| {});
17476
17477    let mut cx = EditorLspTestContext::new_rust(
17478        lsp::ServerCapabilities {
17479            completion_provider: Some(lsp::CompletionOptions {
17480                trigger_characters: Some(vec![".".to_string()]),
17481                resolve_provider: Some(true),
17482                ..Default::default()
17483            }),
17484            ..Default::default()
17485        },
17486        cx,
17487    )
17488    .await;
17489
17490    cx.set_state("fn main() { let a = 2ˇ; }");
17491    cx.simulate_keystroke(".");
17492
17493    let item1 = lsp::CompletionItem {
17494        label: "method id()".to_string(),
17495        filter_text: Some("id".to_string()),
17496        detail: None,
17497        documentation: None,
17498        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17499            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17500            new_text: ".id".to_string(),
17501        })),
17502        ..lsp::CompletionItem::default()
17503    };
17504
17505    let item2 = lsp::CompletionItem {
17506        label: "other".to_string(),
17507        filter_text: Some("other".to_string()),
17508        detail: None,
17509        documentation: None,
17510        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17511            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17512            new_text: ".other".to_string(),
17513        })),
17514        ..lsp::CompletionItem::default()
17515    };
17516
17517    let item1 = item1.clone();
17518    cx.set_request_handler::<lsp::request::Completion, _, _>({
17519        let item1 = item1.clone();
17520        move |_, _, _| {
17521            let item1 = item1.clone();
17522            let item2 = item2.clone();
17523            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17524        }
17525    })
17526    .next()
17527    .await;
17528
17529    cx.condition(|editor, _| editor.context_menu_visible())
17530        .await;
17531    cx.update_editor(|editor, _, _| {
17532        let context_menu = editor.context_menu.borrow_mut();
17533        let context_menu = context_menu
17534            .as_ref()
17535            .expect("Should have the context menu deployed");
17536        match context_menu {
17537            CodeContextMenu::Completions(completions_menu) => {
17538                let completions = completions_menu.completions.borrow_mut();
17539                assert_eq!(
17540                    completions
17541                        .iter()
17542                        .map(|completion| &completion.label.text)
17543                        .collect::<Vec<_>>(),
17544                    vec!["method id()", "other"]
17545                )
17546            }
17547            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17548        }
17549    });
17550
17551    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17552        let item1 = item1.clone();
17553        move |_, item_to_resolve, _| {
17554            let item1 = item1.clone();
17555            async move {
17556                if item1 == item_to_resolve {
17557                    Ok(lsp::CompletionItem {
17558                        label: "method id()".to_string(),
17559                        filter_text: Some("id".to_string()),
17560                        detail: Some("Now resolved!".to_string()),
17561                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17562                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17563                            range: lsp::Range::new(
17564                                lsp::Position::new(0, 22),
17565                                lsp::Position::new(0, 22),
17566                            ),
17567                            new_text: ".id".to_string(),
17568                        })),
17569                        ..lsp::CompletionItem::default()
17570                    })
17571                } else {
17572                    Ok(item_to_resolve)
17573                }
17574            }
17575        }
17576    })
17577    .next()
17578    .await
17579    .unwrap();
17580    cx.run_until_parked();
17581
17582    cx.update_editor(|editor, window, cx| {
17583        editor.context_menu_next(&Default::default(), window, cx);
17584    });
17585
17586    cx.update_editor(|editor, _, _| {
17587        let context_menu = editor.context_menu.borrow_mut();
17588        let context_menu = context_menu
17589            .as_ref()
17590            .expect("Should have the context menu deployed");
17591        match context_menu {
17592            CodeContextMenu::Completions(completions_menu) => {
17593                let completions = completions_menu.completions.borrow_mut();
17594                assert_eq!(
17595                    completions
17596                        .iter()
17597                        .map(|completion| &completion.label.text)
17598                        .collect::<Vec<_>>(),
17599                    vec!["method id() Now resolved!", "other"],
17600                    "Should update first completion label, but not second as the filter text did not match."
17601                );
17602            }
17603            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17604        }
17605    });
17606}
17607
17608#[gpui::test]
17609async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17610    init_test(cx, |_| {});
17611    let mut cx = EditorLspTestContext::new_rust(
17612        lsp::ServerCapabilities {
17613            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17614            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17615            completion_provider: Some(lsp::CompletionOptions {
17616                resolve_provider: Some(true),
17617                ..Default::default()
17618            }),
17619            ..Default::default()
17620        },
17621        cx,
17622    )
17623    .await;
17624    cx.set_state(indoc! {"
17625        struct TestStruct {
17626            field: i32
17627        }
17628
17629        fn mainˇ() {
17630            let unused_var = 42;
17631            let test_struct = TestStruct { field: 42 };
17632        }
17633    "});
17634    let symbol_range = cx.lsp_range(indoc! {"
17635        struct TestStruct {
17636            field: i32
17637        }
17638
17639        «fn main»() {
17640            let unused_var = 42;
17641            let test_struct = TestStruct { field: 42 };
17642        }
17643    "});
17644    let mut hover_requests =
17645        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17646            Ok(Some(lsp::Hover {
17647                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17648                    kind: lsp::MarkupKind::Markdown,
17649                    value: "Function documentation".to_string(),
17650                }),
17651                range: Some(symbol_range),
17652            }))
17653        });
17654
17655    // Case 1: Test that code action menu hide hover popover
17656    cx.dispatch_action(Hover);
17657    hover_requests.next().await;
17658    cx.condition(|editor, _| editor.hover_state.visible()).await;
17659    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17660        move |_, _, _| async move {
17661            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17662                lsp::CodeAction {
17663                    title: "Remove unused variable".to_string(),
17664                    kind: Some(CodeActionKind::QUICKFIX),
17665                    edit: Some(lsp::WorkspaceEdit {
17666                        changes: Some(
17667                            [(
17668                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17669                                vec![lsp::TextEdit {
17670                                    range: lsp::Range::new(
17671                                        lsp::Position::new(5, 4),
17672                                        lsp::Position::new(5, 27),
17673                                    ),
17674                                    new_text: "".to_string(),
17675                                }],
17676                            )]
17677                            .into_iter()
17678                            .collect(),
17679                        ),
17680                        ..Default::default()
17681                    }),
17682                    ..Default::default()
17683                },
17684            )]))
17685        },
17686    );
17687    cx.update_editor(|editor, window, cx| {
17688        editor.toggle_code_actions(
17689            &ToggleCodeActions {
17690                deployed_from: None,
17691                quick_launch: false,
17692            },
17693            window,
17694            cx,
17695        );
17696    });
17697    code_action_requests.next().await;
17698    cx.run_until_parked();
17699    cx.condition(|editor, _| editor.context_menu_visible())
17700        .await;
17701    cx.update_editor(|editor, _, _| {
17702        assert!(
17703            !editor.hover_state.visible(),
17704            "Hover popover should be hidden when code action menu is shown"
17705        );
17706        // Hide code actions
17707        editor.context_menu.take();
17708    });
17709
17710    // Case 2: Test that code completions hide hover popover
17711    cx.dispatch_action(Hover);
17712    hover_requests.next().await;
17713    cx.condition(|editor, _| editor.hover_state.visible()).await;
17714    let counter = Arc::new(AtomicUsize::new(0));
17715    let mut completion_requests =
17716        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17717            let counter = counter.clone();
17718            async move {
17719                counter.fetch_add(1, atomic::Ordering::Release);
17720                Ok(Some(lsp::CompletionResponse::Array(vec![
17721                    lsp::CompletionItem {
17722                        label: "main".into(),
17723                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17724                        detail: Some("() -> ()".to_string()),
17725                        ..Default::default()
17726                    },
17727                    lsp::CompletionItem {
17728                        label: "TestStruct".into(),
17729                        kind: Some(lsp::CompletionItemKind::STRUCT),
17730                        detail: Some("struct TestStruct".to_string()),
17731                        ..Default::default()
17732                    },
17733                ])))
17734            }
17735        });
17736    cx.update_editor(|editor, window, cx| {
17737        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17738    });
17739    completion_requests.next().await;
17740    cx.condition(|editor, _| editor.context_menu_visible())
17741        .await;
17742    cx.update_editor(|editor, _, _| {
17743        assert!(
17744            !editor.hover_state.visible(),
17745            "Hover popover should be hidden when completion menu is shown"
17746        );
17747    });
17748}
17749
17750#[gpui::test]
17751async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17752    init_test(cx, |_| {});
17753
17754    let mut cx = EditorLspTestContext::new_rust(
17755        lsp::ServerCapabilities {
17756            completion_provider: Some(lsp::CompletionOptions {
17757                trigger_characters: Some(vec![".".to_string()]),
17758                resolve_provider: Some(true),
17759                ..Default::default()
17760            }),
17761            ..Default::default()
17762        },
17763        cx,
17764    )
17765    .await;
17766
17767    cx.set_state("fn main() { let a = 2ˇ; }");
17768    cx.simulate_keystroke(".");
17769
17770    let unresolved_item_1 = lsp::CompletionItem {
17771        label: "id".to_string(),
17772        filter_text: Some("id".to_string()),
17773        detail: None,
17774        documentation: None,
17775        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17776            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17777            new_text: ".id".to_string(),
17778        })),
17779        ..lsp::CompletionItem::default()
17780    };
17781    let resolved_item_1 = lsp::CompletionItem {
17782        additional_text_edits: Some(vec![lsp::TextEdit {
17783            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17784            new_text: "!!".to_string(),
17785        }]),
17786        ..unresolved_item_1.clone()
17787    };
17788    let unresolved_item_2 = lsp::CompletionItem {
17789        label: "other".to_string(),
17790        filter_text: Some("other".to_string()),
17791        detail: None,
17792        documentation: None,
17793        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17794            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17795            new_text: ".other".to_string(),
17796        })),
17797        ..lsp::CompletionItem::default()
17798    };
17799    let resolved_item_2 = lsp::CompletionItem {
17800        additional_text_edits: Some(vec![lsp::TextEdit {
17801            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17802            new_text: "??".to_string(),
17803        }]),
17804        ..unresolved_item_2.clone()
17805    };
17806
17807    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17808    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17809    cx.lsp
17810        .server
17811        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17812            let unresolved_item_1 = unresolved_item_1.clone();
17813            let resolved_item_1 = resolved_item_1.clone();
17814            let unresolved_item_2 = unresolved_item_2.clone();
17815            let resolved_item_2 = resolved_item_2.clone();
17816            let resolve_requests_1 = resolve_requests_1.clone();
17817            let resolve_requests_2 = resolve_requests_2.clone();
17818            move |unresolved_request, _| {
17819                let unresolved_item_1 = unresolved_item_1.clone();
17820                let resolved_item_1 = resolved_item_1.clone();
17821                let unresolved_item_2 = unresolved_item_2.clone();
17822                let resolved_item_2 = resolved_item_2.clone();
17823                let resolve_requests_1 = resolve_requests_1.clone();
17824                let resolve_requests_2 = resolve_requests_2.clone();
17825                async move {
17826                    if unresolved_request == unresolved_item_1 {
17827                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17828                        Ok(resolved_item_1.clone())
17829                    } else if unresolved_request == unresolved_item_2 {
17830                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17831                        Ok(resolved_item_2.clone())
17832                    } else {
17833                        panic!("Unexpected completion item {unresolved_request:?}")
17834                    }
17835                }
17836            }
17837        })
17838        .detach();
17839
17840    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17841        let unresolved_item_1 = unresolved_item_1.clone();
17842        let unresolved_item_2 = unresolved_item_2.clone();
17843        async move {
17844            Ok(Some(lsp::CompletionResponse::Array(vec![
17845                unresolved_item_1,
17846                unresolved_item_2,
17847            ])))
17848        }
17849    })
17850    .next()
17851    .await;
17852
17853    cx.condition(|editor, _| editor.context_menu_visible())
17854        .await;
17855    cx.update_editor(|editor, _, _| {
17856        let context_menu = editor.context_menu.borrow_mut();
17857        let context_menu = context_menu
17858            .as_ref()
17859            .expect("Should have the context menu deployed");
17860        match context_menu {
17861            CodeContextMenu::Completions(completions_menu) => {
17862                let completions = completions_menu.completions.borrow_mut();
17863                assert_eq!(
17864                    completions
17865                        .iter()
17866                        .map(|completion| &completion.label.text)
17867                        .collect::<Vec<_>>(),
17868                    vec!["id", "other"]
17869                )
17870            }
17871            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17872        }
17873    });
17874    cx.run_until_parked();
17875
17876    cx.update_editor(|editor, window, cx| {
17877        editor.context_menu_next(&ContextMenuNext, window, cx);
17878    });
17879    cx.run_until_parked();
17880    cx.update_editor(|editor, window, cx| {
17881        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17882    });
17883    cx.run_until_parked();
17884    cx.update_editor(|editor, window, cx| {
17885        editor.context_menu_next(&ContextMenuNext, window, cx);
17886    });
17887    cx.run_until_parked();
17888    cx.update_editor(|editor, window, cx| {
17889        editor
17890            .compose_completion(&ComposeCompletion::default(), window, cx)
17891            .expect("No task returned")
17892    })
17893    .await
17894    .expect("Completion failed");
17895    cx.run_until_parked();
17896
17897    cx.update_editor(|editor, _, cx| {
17898        assert_eq!(
17899            resolve_requests_1.load(atomic::Ordering::Acquire),
17900            1,
17901            "Should always resolve once despite multiple selections"
17902        );
17903        assert_eq!(
17904            resolve_requests_2.load(atomic::Ordering::Acquire),
17905            1,
17906            "Should always resolve once after multiple selections and applying the completion"
17907        );
17908        assert_eq!(
17909            editor.text(cx),
17910            "fn main() { let a = ??.other; }",
17911            "Should use resolved data when applying the completion"
17912        );
17913    });
17914}
17915
17916#[gpui::test]
17917async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17918    init_test(cx, |_| {});
17919
17920    let item_0 = lsp::CompletionItem {
17921        label: "abs".into(),
17922        insert_text: Some("abs".into()),
17923        data: Some(json!({ "very": "special"})),
17924        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17925        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17926            lsp::InsertReplaceEdit {
17927                new_text: "abs".to_string(),
17928                insert: lsp::Range::default(),
17929                replace: lsp::Range::default(),
17930            },
17931        )),
17932        ..lsp::CompletionItem::default()
17933    };
17934    let items = iter::once(item_0.clone())
17935        .chain((11..51).map(|i| lsp::CompletionItem {
17936            label: format!("item_{}", i),
17937            insert_text: Some(format!("item_{}", i)),
17938            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17939            ..lsp::CompletionItem::default()
17940        }))
17941        .collect::<Vec<_>>();
17942
17943    let default_commit_characters = vec!["?".to_string()];
17944    let default_data = json!({ "default": "data"});
17945    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17946    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17947    let default_edit_range = lsp::Range {
17948        start: lsp::Position {
17949            line: 0,
17950            character: 5,
17951        },
17952        end: lsp::Position {
17953            line: 0,
17954            character: 5,
17955        },
17956    };
17957
17958    let mut cx = EditorLspTestContext::new_rust(
17959        lsp::ServerCapabilities {
17960            completion_provider: Some(lsp::CompletionOptions {
17961                trigger_characters: Some(vec![".".to_string()]),
17962                resolve_provider: Some(true),
17963                ..Default::default()
17964            }),
17965            ..Default::default()
17966        },
17967        cx,
17968    )
17969    .await;
17970
17971    cx.set_state("fn main() { let a = 2ˇ; }");
17972    cx.simulate_keystroke(".");
17973
17974    let completion_data = default_data.clone();
17975    let completion_characters = default_commit_characters.clone();
17976    let completion_items = items.clone();
17977    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17978        let default_data = completion_data.clone();
17979        let default_commit_characters = completion_characters.clone();
17980        let items = completion_items.clone();
17981        async move {
17982            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17983                items,
17984                item_defaults: Some(lsp::CompletionListItemDefaults {
17985                    data: Some(default_data.clone()),
17986                    commit_characters: Some(default_commit_characters.clone()),
17987                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17988                        default_edit_range,
17989                    )),
17990                    insert_text_format: Some(default_insert_text_format),
17991                    insert_text_mode: Some(default_insert_text_mode),
17992                }),
17993                ..lsp::CompletionList::default()
17994            })))
17995        }
17996    })
17997    .next()
17998    .await;
17999
18000    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18001    cx.lsp
18002        .server
18003        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18004            let closure_resolved_items = resolved_items.clone();
18005            move |item_to_resolve, _| {
18006                let closure_resolved_items = closure_resolved_items.clone();
18007                async move {
18008                    closure_resolved_items.lock().push(item_to_resolve.clone());
18009                    Ok(item_to_resolve)
18010                }
18011            }
18012        })
18013        .detach();
18014
18015    cx.condition(|editor, _| editor.context_menu_visible())
18016        .await;
18017    cx.run_until_parked();
18018    cx.update_editor(|editor, _, _| {
18019        let menu = editor.context_menu.borrow_mut();
18020        match menu.as_ref().expect("should have the completions menu") {
18021            CodeContextMenu::Completions(completions_menu) => {
18022                assert_eq!(
18023                    completions_menu
18024                        .entries
18025                        .borrow()
18026                        .iter()
18027                        .map(|mat| mat.string.clone())
18028                        .collect::<Vec<String>>(),
18029                    items
18030                        .iter()
18031                        .map(|completion| completion.label.clone())
18032                        .collect::<Vec<String>>()
18033                );
18034            }
18035            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18036        }
18037    });
18038    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18039    // with 4 from the end.
18040    assert_eq!(
18041        *resolved_items.lock(),
18042        [&items[0..16], &items[items.len() - 4..items.len()]]
18043            .concat()
18044            .iter()
18045            .cloned()
18046            .map(|mut item| {
18047                if item.data.is_none() {
18048                    item.data = Some(default_data.clone());
18049                }
18050                item
18051            })
18052            .collect::<Vec<lsp::CompletionItem>>(),
18053        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18054    );
18055    resolved_items.lock().clear();
18056
18057    cx.update_editor(|editor, window, cx| {
18058        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18059    });
18060    cx.run_until_parked();
18061    // Completions that have already been resolved are skipped.
18062    assert_eq!(
18063        *resolved_items.lock(),
18064        items[items.len() - 17..items.len() - 4]
18065            .iter()
18066            .cloned()
18067            .map(|mut item| {
18068                if item.data.is_none() {
18069                    item.data = Some(default_data.clone());
18070                }
18071                item
18072            })
18073            .collect::<Vec<lsp::CompletionItem>>()
18074    );
18075    resolved_items.lock().clear();
18076}
18077
18078#[gpui::test]
18079async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18080    init_test(cx, |_| {});
18081
18082    let mut cx = EditorLspTestContext::new(
18083        Language::new(
18084            LanguageConfig {
18085                matcher: LanguageMatcher {
18086                    path_suffixes: vec!["jsx".into()],
18087                    ..Default::default()
18088                },
18089                overrides: [(
18090                    "element".into(),
18091                    LanguageConfigOverride {
18092                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18093                        ..Default::default()
18094                    },
18095                )]
18096                .into_iter()
18097                .collect(),
18098                ..Default::default()
18099            },
18100            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18101        )
18102        .with_override_query("(jsx_self_closing_element) @element")
18103        .unwrap(),
18104        lsp::ServerCapabilities {
18105            completion_provider: Some(lsp::CompletionOptions {
18106                trigger_characters: Some(vec![":".to_string()]),
18107                ..Default::default()
18108            }),
18109            ..Default::default()
18110        },
18111        cx,
18112    )
18113    .await;
18114
18115    cx.lsp
18116        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18117            Ok(Some(lsp::CompletionResponse::Array(vec![
18118                lsp::CompletionItem {
18119                    label: "bg-blue".into(),
18120                    ..Default::default()
18121                },
18122                lsp::CompletionItem {
18123                    label: "bg-red".into(),
18124                    ..Default::default()
18125                },
18126                lsp::CompletionItem {
18127                    label: "bg-yellow".into(),
18128                    ..Default::default()
18129                },
18130            ])))
18131        });
18132
18133    cx.set_state(r#"<p class="bgˇ" />"#);
18134
18135    // Trigger completion when typing a dash, because the dash is an extra
18136    // word character in the 'element' scope, which contains the cursor.
18137    cx.simulate_keystroke("-");
18138    cx.executor().run_until_parked();
18139    cx.update_editor(|editor, _, _| {
18140        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18141        {
18142            assert_eq!(
18143                completion_menu_entries(menu),
18144                &["bg-blue", "bg-red", "bg-yellow"]
18145            );
18146        } else {
18147            panic!("expected completion menu to be open");
18148        }
18149    });
18150
18151    cx.simulate_keystroke("l");
18152    cx.executor().run_until_parked();
18153    cx.update_editor(|editor, _, _| {
18154        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18155        {
18156            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18157        } else {
18158            panic!("expected completion menu to be open");
18159        }
18160    });
18161
18162    // When filtering completions, consider the character after the '-' to
18163    // be the start of a subword.
18164    cx.set_state(r#"<p class="yelˇ" />"#);
18165    cx.simulate_keystroke("l");
18166    cx.executor().run_until_parked();
18167    cx.update_editor(|editor, _, _| {
18168        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18169        {
18170            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18171        } else {
18172            panic!("expected completion menu to be open");
18173        }
18174    });
18175}
18176
18177fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18178    let entries = menu.entries.borrow();
18179    entries.iter().map(|mat| mat.string.clone()).collect()
18180}
18181
18182#[gpui::test]
18183async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18184    init_test(cx, |settings| {
18185        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
18186    });
18187
18188    let fs = FakeFs::new(cx.executor());
18189    fs.insert_file(path!("/file.ts"), Default::default()).await;
18190
18191    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18192    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18193
18194    language_registry.add(Arc::new(Language::new(
18195        LanguageConfig {
18196            name: "TypeScript".into(),
18197            matcher: LanguageMatcher {
18198                path_suffixes: vec!["ts".to_string()],
18199                ..Default::default()
18200            },
18201            ..Default::default()
18202        },
18203        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18204    )));
18205    update_test_language_settings(cx, |settings| {
18206        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18207    });
18208
18209    let test_plugin = "test_plugin";
18210    let _ = language_registry.register_fake_lsp(
18211        "TypeScript",
18212        FakeLspAdapter {
18213            prettier_plugins: vec![test_plugin],
18214            ..Default::default()
18215        },
18216    );
18217
18218    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18219    let buffer = project
18220        .update(cx, |project, cx| {
18221            project.open_local_buffer(path!("/file.ts"), cx)
18222        })
18223        .await
18224        .unwrap();
18225
18226    let buffer_text = "one\ntwo\nthree\n";
18227    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18228    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18229    editor.update_in(cx, |editor, window, cx| {
18230        editor.set_text(buffer_text, window, cx)
18231    });
18232
18233    editor
18234        .update_in(cx, |editor, window, cx| {
18235            editor.perform_format(
18236                project.clone(),
18237                FormatTrigger::Manual,
18238                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18239                window,
18240                cx,
18241            )
18242        })
18243        .unwrap()
18244        .await;
18245    assert_eq!(
18246        editor.update(cx, |editor, cx| editor.text(cx)),
18247        buffer_text.to_string() + prettier_format_suffix,
18248        "Test prettier formatting was not applied to the original buffer text",
18249    );
18250
18251    update_test_language_settings(cx, |settings| {
18252        settings.defaults.formatter = Some(FormatterList::default())
18253    });
18254    let format = editor.update_in(cx, |editor, window, cx| {
18255        editor.perform_format(
18256            project.clone(),
18257            FormatTrigger::Manual,
18258            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18259            window,
18260            cx,
18261        )
18262    });
18263    format.await.unwrap();
18264    assert_eq!(
18265        editor.update(cx, |editor, cx| editor.text(cx)),
18266        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18267        "Autoformatting (via test prettier) was not applied to the original buffer text",
18268    );
18269}
18270
18271#[gpui::test]
18272async fn test_addition_reverts(cx: &mut TestAppContext) {
18273    init_test(cx, |_| {});
18274    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18275    let base_text = indoc! {r#"
18276        struct Row;
18277        struct Row1;
18278        struct Row2;
18279
18280        struct Row4;
18281        struct Row5;
18282        struct Row6;
18283
18284        struct Row8;
18285        struct Row9;
18286        struct Row10;"#};
18287
18288    // When addition hunks are not adjacent to carets, no hunk revert is performed
18289    assert_hunk_revert(
18290        indoc! {r#"struct Row;
18291                   struct Row1;
18292                   struct Row1.1;
18293                   struct Row1.2;
18294                   struct Row2;ˇ
18295
18296                   struct Row4;
18297                   struct Row5;
18298                   struct Row6;
18299
18300                   struct Row8;
18301                   ˇstruct Row9;
18302                   struct Row9.1;
18303                   struct Row9.2;
18304                   struct Row9.3;
18305                   struct Row10;"#},
18306        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18307        indoc! {r#"struct Row;
18308                   struct Row1;
18309                   struct Row1.1;
18310                   struct Row1.2;
18311                   struct Row2;ˇ
18312
18313                   struct Row4;
18314                   struct Row5;
18315                   struct Row6;
18316
18317                   struct Row8;
18318                   ˇstruct Row9;
18319                   struct Row9.1;
18320                   struct Row9.2;
18321                   struct Row9.3;
18322                   struct Row10;"#},
18323        base_text,
18324        &mut cx,
18325    );
18326    // Same for selections
18327    assert_hunk_revert(
18328        indoc! {r#"struct Row;
18329                   struct Row1;
18330                   struct Row2;
18331                   struct Row2.1;
18332                   struct Row2.2;
18333                   «ˇ
18334                   struct Row4;
18335                   struct» Row5;
18336                   «struct Row6;
18337                   ˇ»
18338                   struct Row9.1;
18339                   struct Row9.2;
18340                   struct Row9.3;
18341                   struct Row8;
18342                   struct Row9;
18343                   struct Row10;"#},
18344        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18345        indoc! {r#"struct Row;
18346                   struct Row1;
18347                   struct Row2;
18348                   struct Row2.1;
18349                   struct Row2.2;
18350                   «ˇ
18351                   struct Row4;
18352                   struct» Row5;
18353                   «struct Row6;
18354                   ˇ»
18355                   struct Row9.1;
18356                   struct Row9.2;
18357                   struct Row9.3;
18358                   struct Row8;
18359                   struct Row9;
18360                   struct Row10;"#},
18361        base_text,
18362        &mut cx,
18363    );
18364
18365    // When carets and selections intersect the addition hunks, those are reverted.
18366    // Adjacent carets got merged.
18367    assert_hunk_revert(
18368        indoc! {r#"struct Row;
18369                   ˇ// something on the top
18370                   struct Row1;
18371                   struct Row2;
18372                   struct Roˇw3.1;
18373                   struct Row2.2;
18374                   struct Row2.3;ˇ
18375
18376                   struct Row4;
18377                   struct ˇRow5.1;
18378                   struct Row5.2;
18379                   struct «Rowˇ»5.3;
18380                   struct Row5;
18381                   struct Row6;
18382                   ˇ
18383                   struct Row9.1;
18384                   struct «Rowˇ»9.2;
18385                   struct «ˇRow»9.3;
18386                   struct Row8;
18387                   struct Row9;
18388                   «ˇ// something on bottom»
18389                   struct Row10;"#},
18390        vec![
18391            DiffHunkStatusKind::Added,
18392            DiffHunkStatusKind::Added,
18393            DiffHunkStatusKind::Added,
18394            DiffHunkStatusKind::Added,
18395            DiffHunkStatusKind::Added,
18396        ],
18397        indoc! {r#"struct Row;
18398                   ˇstruct Row1;
18399                   struct Row2;
18400                   ˇ
18401                   struct Row4;
18402                   ˇstruct Row5;
18403                   struct Row6;
18404                   ˇ
18405                   ˇstruct Row8;
18406                   struct Row9;
18407                   ˇstruct Row10;"#},
18408        base_text,
18409        &mut cx,
18410    );
18411}
18412
18413#[gpui::test]
18414async fn test_modification_reverts(cx: &mut TestAppContext) {
18415    init_test(cx, |_| {});
18416    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18417    let base_text = indoc! {r#"
18418        struct Row;
18419        struct Row1;
18420        struct Row2;
18421
18422        struct Row4;
18423        struct Row5;
18424        struct Row6;
18425
18426        struct Row8;
18427        struct Row9;
18428        struct Row10;"#};
18429
18430    // Modification hunks behave the same as the addition ones.
18431    assert_hunk_revert(
18432        indoc! {r#"struct Row;
18433                   struct Row1;
18434                   struct Row33;
18435                   ˇ
18436                   struct Row4;
18437                   struct Row5;
18438                   struct Row6;
18439                   ˇ
18440                   struct Row99;
18441                   struct Row9;
18442                   struct Row10;"#},
18443        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18444        indoc! {r#"struct Row;
18445                   struct Row1;
18446                   struct Row33;
18447                   ˇ
18448                   struct Row4;
18449                   struct Row5;
18450                   struct Row6;
18451                   ˇ
18452                   struct Row99;
18453                   struct Row9;
18454                   struct Row10;"#},
18455        base_text,
18456        &mut cx,
18457    );
18458    assert_hunk_revert(
18459        indoc! {r#"struct Row;
18460                   struct Row1;
18461                   struct Row33;
18462                   «ˇ
18463                   struct Row4;
18464                   struct» Row5;
18465                   «struct Row6;
18466                   ˇ»
18467                   struct Row99;
18468                   struct Row9;
18469                   struct Row10;"#},
18470        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18471        indoc! {r#"struct Row;
18472                   struct Row1;
18473                   struct Row33;
18474                   «ˇ
18475                   struct Row4;
18476                   struct» Row5;
18477                   «struct Row6;
18478                   ˇ»
18479                   struct Row99;
18480                   struct Row9;
18481                   struct Row10;"#},
18482        base_text,
18483        &mut cx,
18484    );
18485
18486    assert_hunk_revert(
18487        indoc! {r#"ˇstruct Row1.1;
18488                   struct Row1;
18489                   «ˇstr»uct Row22;
18490
18491                   struct ˇRow44;
18492                   struct Row5;
18493                   struct «Rˇ»ow66;ˇ
18494
18495                   «struˇ»ct Row88;
18496                   struct Row9;
18497                   struct Row1011;ˇ"#},
18498        vec![
18499            DiffHunkStatusKind::Modified,
18500            DiffHunkStatusKind::Modified,
18501            DiffHunkStatusKind::Modified,
18502            DiffHunkStatusKind::Modified,
18503            DiffHunkStatusKind::Modified,
18504            DiffHunkStatusKind::Modified,
18505        ],
18506        indoc! {r#"struct Row;
18507                   ˇstruct Row1;
18508                   struct Row2;
18509                   ˇ
18510                   struct Row4;
18511                   ˇstruct Row5;
18512                   struct Row6;
18513                   ˇ
18514                   struct Row8;
18515                   ˇstruct Row9;
18516                   struct Row10;ˇ"#},
18517        base_text,
18518        &mut cx,
18519    );
18520}
18521
18522#[gpui::test]
18523async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18524    init_test(cx, |_| {});
18525    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18526    let base_text = indoc! {r#"
18527        one
18528
18529        two
18530        three
18531        "#};
18532
18533    cx.set_head_text(base_text);
18534    cx.set_state("\nˇ\n");
18535    cx.executor().run_until_parked();
18536    cx.update_editor(|editor, _window, cx| {
18537        editor.expand_selected_diff_hunks(cx);
18538    });
18539    cx.executor().run_until_parked();
18540    cx.update_editor(|editor, window, cx| {
18541        editor.backspace(&Default::default(), window, cx);
18542    });
18543    cx.run_until_parked();
18544    cx.assert_state_with_diff(
18545        indoc! {r#"
18546
18547        - two
18548        - threeˇ
18549        +
18550        "#}
18551        .to_string(),
18552    );
18553}
18554
18555#[gpui::test]
18556async fn test_deletion_reverts(cx: &mut TestAppContext) {
18557    init_test(cx, |_| {});
18558    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18559    let base_text = indoc! {r#"struct Row;
18560struct Row1;
18561struct Row2;
18562
18563struct Row4;
18564struct Row5;
18565struct Row6;
18566
18567struct Row8;
18568struct Row9;
18569struct Row10;"#};
18570
18571    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18572    assert_hunk_revert(
18573        indoc! {r#"struct Row;
18574                   struct Row2;
18575
18576                   ˇstruct Row4;
18577                   struct Row5;
18578                   struct Row6;
18579                   ˇ
18580                   struct Row8;
18581                   struct Row10;"#},
18582        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18583        indoc! {r#"struct Row;
18584                   struct Row2;
18585
18586                   ˇstruct Row4;
18587                   struct Row5;
18588                   struct Row6;
18589                   ˇ
18590                   struct Row8;
18591                   struct Row10;"#},
18592        base_text,
18593        &mut cx,
18594    );
18595    assert_hunk_revert(
18596        indoc! {r#"struct Row;
18597                   struct Row2;
18598
18599                   «ˇstruct Row4;
18600                   struct» Row5;
18601                   «struct Row6;
18602                   ˇ»
18603                   struct Row8;
18604                   struct Row10;"#},
18605        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18606        indoc! {r#"struct Row;
18607                   struct Row2;
18608
18609                   «ˇstruct Row4;
18610                   struct» Row5;
18611                   «struct Row6;
18612                   ˇ»
18613                   struct Row8;
18614                   struct Row10;"#},
18615        base_text,
18616        &mut cx,
18617    );
18618
18619    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18620    assert_hunk_revert(
18621        indoc! {r#"struct Row;
18622                   ˇstruct Row2;
18623
18624                   struct Row4;
18625                   struct Row5;
18626                   struct Row6;
18627
18628                   struct Row8;ˇ
18629                   struct Row10;"#},
18630        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18631        indoc! {r#"struct Row;
18632                   struct Row1;
18633                   ˇstruct Row2;
18634
18635                   struct Row4;
18636                   struct Row5;
18637                   struct Row6;
18638
18639                   struct Row8;ˇ
18640                   struct Row9;
18641                   struct Row10;"#},
18642        base_text,
18643        &mut cx,
18644    );
18645    assert_hunk_revert(
18646        indoc! {r#"struct Row;
18647                   struct Row2«ˇ;
18648                   struct Row4;
18649                   struct» Row5;
18650                   «struct Row6;
18651
18652                   struct Row8;ˇ»
18653                   struct Row10;"#},
18654        vec![
18655            DiffHunkStatusKind::Deleted,
18656            DiffHunkStatusKind::Deleted,
18657            DiffHunkStatusKind::Deleted,
18658        ],
18659        indoc! {r#"struct Row;
18660                   struct Row1;
18661                   struct Row2«ˇ;
18662
18663                   struct Row4;
18664                   struct» Row5;
18665                   «struct Row6;
18666
18667                   struct Row8;ˇ»
18668                   struct Row9;
18669                   struct Row10;"#},
18670        base_text,
18671        &mut cx,
18672    );
18673}
18674
18675#[gpui::test]
18676async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18677    init_test(cx, |_| {});
18678
18679    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18680    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18681    let base_text_3 =
18682        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18683
18684    let text_1 = edit_first_char_of_every_line(base_text_1);
18685    let text_2 = edit_first_char_of_every_line(base_text_2);
18686    let text_3 = edit_first_char_of_every_line(base_text_3);
18687
18688    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18689    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18690    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18691
18692    let multibuffer = cx.new(|cx| {
18693        let mut multibuffer = MultiBuffer::new(ReadWrite);
18694        multibuffer.push_excerpts(
18695            buffer_1.clone(),
18696            [
18697                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18698                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18699                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18700            ],
18701            cx,
18702        );
18703        multibuffer.push_excerpts(
18704            buffer_2.clone(),
18705            [
18706                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18707                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18708                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18709            ],
18710            cx,
18711        );
18712        multibuffer.push_excerpts(
18713            buffer_3.clone(),
18714            [
18715                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18716                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18717                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18718            ],
18719            cx,
18720        );
18721        multibuffer
18722    });
18723
18724    let fs = FakeFs::new(cx.executor());
18725    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18726    let (editor, cx) = cx
18727        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18728    editor.update_in(cx, |editor, _window, cx| {
18729        for (buffer, diff_base) in [
18730            (buffer_1.clone(), base_text_1),
18731            (buffer_2.clone(), base_text_2),
18732            (buffer_3.clone(), base_text_3),
18733        ] {
18734            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18735            editor
18736                .buffer
18737                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18738        }
18739    });
18740    cx.executor().run_until_parked();
18741
18742    editor.update_in(cx, |editor, window, cx| {
18743        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}");
18744        editor.select_all(&SelectAll, window, cx);
18745        editor.git_restore(&Default::default(), window, cx);
18746    });
18747    cx.executor().run_until_parked();
18748
18749    // When all ranges are selected, all buffer hunks are reverted.
18750    editor.update(cx, |editor, cx| {
18751        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");
18752    });
18753    buffer_1.update(cx, |buffer, _| {
18754        assert_eq!(buffer.text(), base_text_1);
18755    });
18756    buffer_2.update(cx, |buffer, _| {
18757        assert_eq!(buffer.text(), base_text_2);
18758    });
18759    buffer_3.update(cx, |buffer, _| {
18760        assert_eq!(buffer.text(), base_text_3);
18761    });
18762
18763    editor.update_in(cx, |editor, window, cx| {
18764        editor.undo(&Default::default(), window, cx);
18765    });
18766
18767    editor.update_in(cx, |editor, window, cx| {
18768        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18769            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18770        });
18771        editor.git_restore(&Default::default(), window, cx);
18772    });
18773
18774    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18775    // but not affect buffer_2 and its related excerpts.
18776    editor.update(cx, |editor, cx| {
18777        assert_eq!(
18778            editor.text(cx),
18779            "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}"
18780        );
18781    });
18782    buffer_1.update(cx, |buffer, _| {
18783        assert_eq!(buffer.text(), base_text_1);
18784    });
18785    buffer_2.update(cx, |buffer, _| {
18786        assert_eq!(
18787            buffer.text(),
18788            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18789        );
18790    });
18791    buffer_3.update(cx, |buffer, _| {
18792        assert_eq!(
18793            buffer.text(),
18794            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18795        );
18796    });
18797
18798    fn edit_first_char_of_every_line(text: &str) -> String {
18799        text.split('\n')
18800            .map(|line| format!("X{}", &line[1..]))
18801            .collect::<Vec<_>>()
18802            .join("\n")
18803    }
18804}
18805
18806#[gpui::test]
18807async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18808    init_test(cx, |_| {});
18809
18810    let cols = 4;
18811    let rows = 10;
18812    let sample_text_1 = sample_text(rows, cols, 'a');
18813    assert_eq!(
18814        sample_text_1,
18815        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18816    );
18817    let sample_text_2 = sample_text(rows, cols, 'l');
18818    assert_eq!(
18819        sample_text_2,
18820        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18821    );
18822    let sample_text_3 = sample_text(rows, cols, 'v');
18823    assert_eq!(
18824        sample_text_3,
18825        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18826    );
18827
18828    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18829    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18830    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18831
18832    let multi_buffer = cx.new(|cx| {
18833        let mut multibuffer = MultiBuffer::new(ReadWrite);
18834        multibuffer.push_excerpts(
18835            buffer_1.clone(),
18836            [
18837                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18838                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18839                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18840            ],
18841            cx,
18842        );
18843        multibuffer.push_excerpts(
18844            buffer_2.clone(),
18845            [
18846                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18847                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18848                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18849            ],
18850            cx,
18851        );
18852        multibuffer.push_excerpts(
18853            buffer_3.clone(),
18854            [
18855                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18856                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18857                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18858            ],
18859            cx,
18860        );
18861        multibuffer
18862    });
18863
18864    let fs = FakeFs::new(cx.executor());
18865    fs.insert_tree(
18866        "/a",
18867        json!({
18868            "main.rs": sample_text_1,
18869            "other.rs": sample_text_2,
18870            "lib.rs": sample_text_3,
18871        }),
18872    )
18873    .await;
18874    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18875    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18876    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18877    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18878        Editor::new(
18879            EditorMode::full(),
18880            multi_buffer,
18881            Some(project.clone()),
18882            window,
18883            cx,
18884        )
18885    });
18886    let multibuffer_item_id = workspace
18887        .update(cx, |workspace, window, cx| {
18888            assert!(
18889                workspace.active_item(cx).is_none(),
18890                "active item should be None before the first item is added"
18891            );
18892            workspace.add_item_to_active_pane(
18893                Box::new(multi_buffer_editor.clone()),
18894                None,
18895                true,
18896                window,
18897                cx,
18898            );
18899            let active_item = workspace
18900                .active_item(cx)
18901                .expect("should have an active item after adding the multi buffer");
18902            assert_eq!(
18903                active_item.buffer_kind(cx),
18904                ItemBufferKind::Multibuffer,
18905                "A multi buffer was expected to active after adding"
18906            );
18907            active_item.item_id()
18908        })
18909        .unwrap();
18910    cx.executor().run_until_parked();
18911
18912    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18913        editor.change_selections(
18914            SelectionEffects::scroll(Autoscroll::Next),
18915            window,
18916            cx,
18917            |s| s.select_ranges(Some(1..2)),
18918        );
18919        editor.open_excerpts(&OpenExcerpts, window, cx);
18920    });
18921    cx.executor().run_until_parked();
18922    let first_item_id = workspace
18923        .update(cx, |workspace, window, cx| {
18924            let active_item = workspace
18925                .active_item(cx)
18926                .expect("should have an active item after navigating into the 1st buffer");
18927            let first_item_id = active_item.item_id();
18928            assert_ne!(
18929                first_item_id, multibuffer_item_id,
18930                "Should navigate into the 1st buffer and activate it"
18931            );
18932            assert_eq!(
18933                active_item.buffer_kind(cx),
18934                ItemBufferKind::Singleton,
18935                "New active item should be a singleton buffer"
18936            );
18937            assert_eq!(
18938                active_item
18939                    .act_as::<Editor>(cx)
18940                    .expect("should have navigated into an editor for the 1st buffer")
18941                    .read(cx)
18942                    .text(cx),
18943                sample_text_1
18944            );
18945
18946            workspace
18947                .go_back(workspace.active_pane().downgrade(), window, cx)
18948                .detach_and_log_err(cx);
18949
18950            first_item_id
18951        })
18952        .unwrap();
18953    cx.executor().run_until_parked();
18954    workspace
18955        .update(cx, |workspace, _, cx| {
18956            let active_item = workspace
18957                .active_item(cx)
18958                .expect("should have an active item after navigating back");
18959            assert_eq!(
18960                active_item.item_id(),
18961                multibuffer_item_id,
18962                "Should navigate back to the multi buffer"
18963            );
18964            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
18965        })
18966        .unwrap();
18967
18968    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18969        editor.change_selections(
18970            SelectionEffects::scroll(Autoscroll::Next),
18971            window,
18972            cx,
18973            |s| s.select_ranges(Some(39..40)),
18974        );
18975        editor.open_excerpts(&OpenExcerpts, window, cx);
18976    });
18977    cx.executor().run_until_parked();
18978    let second_item_id = workspace
18979        .update(cx, |workspace, window, cx| {
18980            let active_item = workspace
18981                .active_item(cx)
18982                .expect("should have an active item after navigating into the 2nd buffer");
18983            let second_item_id = active_item.item_id();
18984            assert_ne!(
18985                second_item_id, multibuffer_item_id,
18986                "Should navigate away from the multibuffer"
18987            );
18988            assert_ne!(
18989                second_item_id, first_item_id,
18990                "Should navigate into the 2nd buffer and activate it"
18991            );
18992            assert_eq!(
18993                active_item.buffer_kind(cx),
18994                ItemBufferKind::Singleton,
18995                "New active item should be a singleton buffer"
18996            );
18997            assert_eq!(
18998                active_item
18999                    .act_as::<Editor>(cx)
19000                    .expect("should have navigated into an editor")
19001                    .read(cx)
19002                    .text(cx),
19003                sample_text_2
19004            );
19005
19006            workspace
19007                .go_back(workspace.active_pane().downgrade(), window, cx)
19008                .detach_and_log_err(cx);
19009
19010            second_item_id
19011        })
19012        .unwrap();
19013    cx.executor().run_until_parked();
19014    workspace
19015        .update(cx, |workspace, _, cx| {
19016            let active_item = workspace
19017                .active_item(cx)
19018                .expect("should have an active item after navigating back from the 2nd buffer");
19019            assert_eq!(
19020                active_item.item_id(),
19021                multibuffer_item_id,
19022                "Should navigate back from the 2nd buffer to the multi buffer"
19023            );
19024            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19025        })
19026        .unwrap();
19027
19028    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19029        editor.change_selections(
19030            SelectionEffects::scroll(Autoscroll::Next),
19031            window,
19032            cx,
19033            |s| s.select_ranges(Some(70..70)),
19034        );
19035        editor.open_excerpts(&OpenExcerpts, window, cx);
19036    });
19037    cx.executor().run_until_parked();
19038    workspace
19039        .update(cx, |workspace, window, cx| {
19040            let active_item = workspace
19041                .active_item(cx)
19042                .expect("should have an active item after navigating into the 3rd buffer");
19043            let third_item_id = active_item.item_id();
19044            assert_ne!(
19045                third_item_id, multibuffer_item_id,
19046                "Should navigate into the 3rd buffer and activate it"
19047            );
19048            assert_ne!(third_item_id, first_item_id);
19049            assert_ne!(third_item_id, second_item_id);
19050            assert_eq!(
19051                active_item.buffer_kind(cx),
19052                ItemBufferKind::Singleton,
19053                "New active item should be a singleton buffer"
19054            );
19055            assert_eq!(
19056                active_item
19057                    .act_as::<Editor>(cx)
19058                    .expect("should have navigated into an editor")
19059                    .read(cx)
19060                    .text(cx),
19061                sample_text_3
19062            );
19063
19064            workspace
19065                .go_back(workspace.active_pane().downgrade(), window, cx)
19066                .detach_and_log_err(cx);
19067        })
19068        .unwrap();
19069    cx.executor().run_until_parked();
19070    workspace
19071        .update(cx, |workspace, _, cx| {
19072            let active_item = workspace
19073                .active_item(cx)
19074                .expect("should have an active item after navigating back from the 3rd buffer");
19075            assert_eq!(
19076                active_item.item_id(),
19077                multibuffer_item_id,
19078                "Should navigate back from the 3rd buffer to the multi buffer"
19079            );
19080            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19081        })
19082        .unwrap();
19083}
19084
19085#[gpui::test]
19086async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19087    init_test(cx, |_| {});
19088
19089    let mut cx = EditorTestContext::new(cx).await;
19090
19091    let diff_base = r#"
19092        use some::mod;
19093
19094        const A: u32 = 42;
19095
19096        fn main() {
19097            println!("hello");
19098
19099            println!("world");
19100        }
19101        "#
19102    .unindent();
19103
19104    cx.set_state(
19105        &r#"
19106        use some::modified;
19107
19108        ˇ
19109        fn main() {
19110            println!("hello there");
19111
19112            println!("around the");
19113            println!("world");
19114        }
19115        "#
19116        .unindent(),
19117    );
19118
19119    cx.set_head_text(&diff_base);
19120    executor.run_until_parked();
19121
19122    cx.update_editor(|editor, window, cx| {
19123        editor.go_to_next_hunk(&GoToHunk, window, cx);
19124        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19125    });
19126    executor.run_until_parked();
19127    cx.assert_state_with_diff(
19128        r#"
19129          use some::modified;
19130
19131
19132          fn main() {
19133        -     println!("hello");
19134        + ˇ    println!("hello there");
19135
19136              println!("around the");
19137              println!("world");
19138          }
19139        "#
19140        .unindent(),
19141    );
19142
19143    cx.update_editor(|editor, window, cx| {
19144        for _ in 0..2 {
19145            editor.go_to_next_hunk(&GoToHunk, window, cx);
19146            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19147        }
19148    });
19149    executor.run_until_parked();
19150    cx.assert_state_with_diff(
19151        r#"
19152        - use some::mod;
19153        + ˇuse some::modified;
19154
19155
19156          fn main() {
19157        -     println!("hello");
19158        +     println!("hello there");
19159
19160        +     println!("around the");
19161              println!("world");
19162          }
19163        "#
19164        .unindent(),
19165    );
19166
19167    cx.update_editor(|editor, window, cx| {
19168        editor.go_to_next_hunk(&GoToHunk, window, cx);
19169        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19170    });
19171    executor.run_until_parked();
19172    cx.assert_state_with_diff(
19173        r#"
19174        - use some::mod;
19175        + use some::modified;
19176
19177        - const A: u32 = 42;
19178          ˇ
19179          fn main() {
19180        -     println!("hello");
19181        +     println!("hello there");
19182
19183        +     println!("around the");
19184              println!("world");
19185          }
19186        "#
19187        .unindent(),
19188    );
19189
19190    cx.update_editor(|editor, window, cx| {
19191        editor.cancel(&Cancel, window, cx);
19192    });
19193
19194    cx.assert_state_with_diff(
19195        r#"
19196          use some::modified;
19197
19198          ˇ
19199          fn main() {
19200              println!("hello there");
19201
19202              println!("around the");
19203              println!("world");
19204          }
19205        "#
19206        .unindent(),
19207    );
19208}
19209
19210#[gpui::test]
19211async fn test_diff_base_change_with_expanded_diff_hunks(
19212    executor: BackgroundExecutor,
19213    cx: &mut TestAppContext,
19214) {
19215    init_test(cx, |_| {});
19216
19217    let mut cx = EditorTestContext::new(cx).await;
19218
19219    let diff_base = r#"
19220        use some::mod1;
19221        use some::mod2;
19222
19223        const A: u32 = 42;
19224        const B: u32 = 42;
19225        const C: u32 = 42;
19226
19227        fn main() {
19228            println!("hello");
19229
19230            println!("world");
19231        }
19232        "#
19233    .unindent();
19234
19235    cx.set_state(
19236        &r#"
19237        use some::mod2;
19238
19239        const A: u32 = 42;
19240        const C: u32 = 42;
19241
19242        fn main(ˇ) {
19243            //println!("hello");
19244
19245            println!("world");
19246            //
19247            //
19248        }
19249        "#
19250        .unindent(),
19251    );
19252
19253    cx.set_head_text(&diff_base);
19254    executor.run_until_parked();
19255
19256    cx.update_editor(|editor, window, cx| {
19257        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19258    });
19259    executor.run_until_parked();
19260    cx.assert_state_with_diff(
19261        r#"
19262        - use some::mod1;
19263          use some::mod2;
19264
19265          const A: u32 = 42;
19266        - const B: u32 = 42;
19267          const C: u32 = 42;
19268
19269          fn main(ˇ) {
19270        -     println!("hello");
19271        +     //println!("hello");
19272
19273              println!("world");
19274        +     //
19275        +     //
19276          }
19277        "#
19278        .unindent(),
19279    );
19280
19281    cx.set_head_text("new diff base!");
19282    executor.run_until_parked();
19283    cx.assert_state_with_diff(
19284        r#"
19285        - new diff base!
19286        + use some::mod2;
19287        +
19288        + const A: u32 = 42;
19289        + const C: u32 = 42;
19290        +
19291        + fn main(ˇ) {
19292        +     //println!("hello");
19293        +
19294        +     println!("world");
19295        +     //
19296        +     //
19297        + }
19298        "#
19299        .unindent(),
19300    );
19301}
19302
19303#[gpui::test]
19304async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19305    init_test(cx, |_| {});
19306
19307    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19308    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19309    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19310    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19311    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19312    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19313
19314    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19315    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19316    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19317
19318    let multi_buffer = cx.new(|cx| {
19319        let mut multibuffer = MultiBuffer::new(ReadWrite);
19320        multibuffer.push_excerpts(
19321            buffer_1.clone(),
19322            [
19323                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19324                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19325                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19326            ],
19327            cx,
19328        );
19329        multibuffer.push_excerpts(
19330            buffer_2.clone(),
19331            [
19332                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19333                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19334                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19335            ],
19336            cx,
19337        );
19338        multibuffer.push_excerpts(
19339            buffer_3.clone(),
19340            [
19341                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19342                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19343                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19344            ],
19345            cx,
19346        );
19347        multibuffer
19348    });
19349
19350    let editor =
19351        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19352    editor
19353        .update(cx, |editor, _window, cx| {
19354            for (buffer, diff_base) in [
19355                (buffer_1.clone(), file_1_old),
19356                (buffer_2.clone(), file_2_old),
19357                (buffer_3.clone(), file_3_old),
19358            ] {
19359                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19360                editor
19361                    .buffer
19362                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19363            }
19364        })
19365        .unwrap();
19366
19367    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19368    cx.run_until_parked();
19369
19370    cx.assert_editor_state(
19371        &"
19372            ˇaaa
19373            ccc
19374            ddd
19375
19376            ggg
19377            hhh
19378
19379
19380            lll
19381            mmm
19382            NNN
19383
19384            qqq
19385            rrr
19386
19387            uuu
19388            111
19389            222
19390            333
19391
19392            666
19393            777
19394
19395            000
19396            !!!"
19397        .unindent(),
19398    );
19399
19400    cx.update_editor(|editor, window, cx| {
19401        editor.select_all(&SelectAll, window, cx);
19402        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19403    });
19404    cx.executor().run_until_parked();
19405
19406    cx.assert_state_with_diff(
19407        "
19408            «aaa
19409          - bbb
19410            ccc
19411            ddd
19412
19413            ggg
19414            hhh
19415
19416
19417            lll
19418            mmm
19419          - nnn
19420          + NNN
19421
19422            qqq
19423            rrr
19424
19425            uuu
19426            111
19427            222
19428            333
19429
19430          + 666
19431            777
19432
19433            000
19434            !!!ˇ»"
19435            .unindent(),
19436    );
19437}
19438
19439#[gpui::test]
19440async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19441    init_test(cx, |_| {});
19442
19443    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19444    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19445
19446    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19447    let multi_buffer = cx.new(|cx| {
19448        let mut multibuffer = MultiBuffer::new(ReadWrite);
19449        multibuffer.push_excerpts(
19450            buffer.clone(),
19451            [
19452                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19453                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19454                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19455            ],
19456            cx,
19457        );
19458        multibuffer
19459    });
19460
19461    let editor =
19462        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19463    editor
19464        .update(cx, |editor, _window, cx| {
19465            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19466            editor
19467                .buffer
19468                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19469        })
19470        .unwrap();
19471
19472    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19473    cx.run_until_parked();
19474
19475    cx.update_editor(|editor, window, cx| {
19476        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19477    });
19478    cx.executor().run_until_parked();
19479
19480    // When the start of a hunk coincides with the start of its excerpt,
19481    // the hunk is expanded. When the start of a hunk is earlier than
19482    // the start of its excerpt, the hunk is not expanded.
19483    cx.assert_state_with_diff(
19484        "
19485            ˇaaa
19486          - bbb
19487          + BBB
19488
19489          - ddd
19490          - eee
19491          + DDD
19492          + EEE
19493            fff
19494
19495            iii
19496        "
19497        .unindent(),
19498    );
19499}
19500
19501#[gpui::test]
19502async fn test_edits_around_expanded_insertion_hunks(
19503    executor: BackgroundExecutor,
19504    cx: &mut TestAppContext,
19505) {
19506    init_test(cx, |_| {});
19507
19508    let mut cx = EditorTestContext::new(cx).await;
19509
19510    let diff_base = r#"
19511        use some::mod1;
19512        use some::mod2;
19513
19514        const A: u32 = 42;
19515
19516        fn main() {
19517            println!("hello");
19518
19519            println!("world");
19520        }
19521        "#
19522    .unindent();
19523    executor.run_until_parked();
19524    cx.set_state(
19525        &r#"
19526        use some::mod1;
19527        use some::mod2;
19528
19529        const A: u32 = 42;
19530        const B: u32 = 42;
19531        const C: u32 = 42;
19532        ˇ
19533
19534        fn main() {
19535            println!("hello");
19536
19537            println!("world");
19538        }
19539        "#
19540        .unindent(),
19541    );
19542
19543    cx.set_head_text(&diff_base);
19544    executor.run_until_parked();
19545
19546    cx.update_editor(|editor, window, cx| {
19547        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19548    });
19549    executor.run_until_parked();
19550
19551    cx.assert_state_with_diff(
19552        r#"
19553        use some::mod1;
19554        use some::mod2;
19555
19556        const A: u32 = 42;
19557      + const B: u32 = 42;
19558      + const C: u32 = 42;
19559      + ˇ
19560
19561        fn main() {
19562            println!("hello");
19563
19564            println!("world");
19565        }
19566      "#
19567        .unindent(),
19568    );
19569
19570    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19571    executor.run_until_parked();
19572
19573    cx.assert_state_with_diff(
19574        r#"
19575        use some::mod1;
19576        use some::mod2;
19577
19578        const A: u32 = 42;
19579      + const B: u32 = 42;
19580      + const C: u32 = 42;
19581      + const D: u32 = 42;
19582      + ˇ
19583
19584        fn main() {
19585            println!("hello");
19586
19587            println!("world");
19588        }
19589      "#
19590        .unindent(),
19591    );
19592
19593    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19594    executor.run_until_parked();
19595
19596    cx.assert_state_with_diff(
19597        r#"
19598        use some::mod1;
19599        use some::mod2;
19600
19601        const A: u32 = 42;
19602      + const B: u32 = 42;
19603      + const C: u32 = 42;
19604      + const D: u32 = 42;
19605      + const E: u32 = 42;
19606      + ˇ
19607
19608        fn main() {
19609            println!("hello");
19610
19611            println!("world");
19612        }
19613      "#
19614        .unindent(),
19615    );
19616
19617    cx.update_editor(|editor, window, cx| {
19618        editor.delete_line(&DeleteLine, window, cx);
19619    });
19620    executor.run_until_parked();
19621
19622    cx.assert_state_with_diff(
19623        r#"
19624        use some::mod1;
19625        use some::mod2;
19626
19627        const A: u32 = 42;
19628      + const B: u32 = 42;
19629      + const C: u32 = 42;
19630      + const D: u32 = 42;
19631      + const E: u32 = 42;
19632        ˇ
19633        fn main() {
19634            println!("hello");
19635
19636            println!("world");
19637        }
19638      "#
19639        .unindent(),
19640    );
19641
19642    cx.update_editor(|editor, window, cx| {
19643        editor.move_up(&MoveUp, window, cx);
19644        editor.delete_line(&DeleteLine, window, cx);
19645        editor.move_up(&MoveUp, window, cx);
19646        editor.delete_line(&DeleteLine, window, cx);
19647        editor.move_up(&MoveUp, window, cx);
19648        editor.delete_line(&DeleteLine, window, cx);
19649    });
19650    executor.run_until_parked();
19651    cx.assert_state_with_diff(
19652        r#"
19653        use some::mod1;
19654        use some::mod2;
19655
19656        const A: u32 = 42;
19657      + const B: u32 = 42;
19658        ˇ
19659        fn main() {
19660            println!("hello");
19661
19662            println!("world");
19663        }
19664      "#
19665        .unindent(),
19666    );
19667
19668    cx.update_editor(|editor, window, cx| {
19669        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19670        editor.delete_line(&DeleteLine, window, cx);
19671    });
19672    executor.run_until_parked();
19673    cx.assert_state_with_diff(
19674        r#"
19675        ˇ
19676        fn main() {
19677            println!("hello");
19678
19679            println!("world");
19680        }
19681      "#
19682        .unindent(),
19683    );
19684}
19685
19686#[gpui::test]
19687async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19688    init_test(cx, |_| {});
19689
19690    let mut cx = EditorTestContext::new(cx).await;
19691    cx.set_head_text(indoc! { "
19692        one
19693        two
19694        three
19695        four
19696        five
19697        "
19698    });
19699    cx.set_state(indoc! { "
19700        one
19701        ˇthree
19702        five
19703    "});
19704    cx.run_until_parked();
19705    cx.update_editor(|editor, window, cx| {
19706        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19707    });
19708    cx.assert_state_with_diff(
19709        indoc! { "
19710        one
19711      - two
19712        ˇthree
19713      - four
19714        five
19715    "}
19716        .to_string(),
19717    );
19718    cx.update_editor(|editor, window, cx| {
19719        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19720    });
19721
19722    cx.assert_state_with_diff(
19723        indoc! { "
19724        one
19725        ˇthree
19726        five
19727    "}
19728        .to_string(),
19729    );
19730
19731    cx.set_state(indoc! { "
19732        one
19733        ˇTWO
19734        three
19735        four
19736        five
19737    "});
19738    cx.run_until_parked();
19739    cx.update_editor(|editor, window, cx| {
19740        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19741    });
19742
19743    cx.assert_state_with_diff(
19744        indoc! { "
19745            one
19746          - two
19747          + ˇTWO
19748            three
19749            four
19750            five
19751        "}
19752        .to_string(),
19753    );
19754    cx.update_editor(|editor, window, cx| {
19755        editor.move_up(&Default::default(), window, cx);
19756        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19757    });
19758    cx.assert_state_with_diff(
19759        indoc! { "
19760            one
19761            ˇTWO
19762            three
19763            four
19764            five
19765        "}
19766        .to_string(),
19767    );
19768}
19769
19770#[gpui::test]
19771async fn test_edits_around_expanded_deletion_hunks(
19772    executor: BackgroundExecutor,
19773    cx: &mut TestAppContext,
19774) {
19775    init_test(cx, |_| {});
19776
19777    let mut cx = EditorTestContext::new(cx).await;
19778
19779    let diff_base = r#"
19780        use some::mod1;
19781        use some::mod2;
19782
19783        const A: u32 = 42;
19784        const B: u32 = 42;
19785        const C: u32 = 42;
19786
19787
19788        fn main() {
19789            println!("hello");
19790
19791            println!("world");
19792        }
19793    "#
19794    .unindent();
19795    executor.run_until_parked();
19796    cx.set_state(
19797        &r#"
19798        use some::mod1;
19799        use some::mod2;
19800
19801        ˇconst B: u32 = 42;
19802        const C: u32 = 42;
19803
19804
19805        fn main() {
19806            println!("hello");
19807
19808            println!("world");
19809        }
19810        "#
19811        .unindent(),
19812    );
19813
19814    cx.set_head_text(&diff_base);
19815    executor.run_until_parked();
19816
19817    cx.update_editor(|editor, window, cx| {
19818        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19819    });
19820    executor.run_until_parked();
19821
19822    cx.assert_state_with_diff(
19823        r#"
19824        use some::mod1;
19825        use some::mod2;
19826
19827      - const A: u32 = 42;
19828        ˇconst B: u32 = 42;
19829        const C: u32 = 42;
19830
19831
19832        fn main() {
19833            println!("hello");
19834
19835            println!("world");
19836        }
19837      "#
19838        .unindent(),
19839    );
19840
19841    cx.update_editor(|editor, window, cx| {
19842        editor.delete_line(&DeleteLine, window, cx);
19843    });
19844    executor.run_until_parked();
19845    cx.assert_state_with_diff(
19846        r#"
19847        use some::mod1;
19848        use some::mod2;
19849
19850      - const A: u32 = 42;
19851      - const B: u32 = 42;
19852        ˇconst C: u32 = 42;
19853
19854
19855        fn main() {
19856            println!("hello");
19857
19858            println!("world");
19859        }
19860      "#
19861        .unindent(),
19862    );
19863
19864    cx.update_editor(|editor, window, cx| {
19865        editor.delete_line(&DeleteLine, window, cx);
19866    });
19867    executor.run_until_parked();
19868    cx.assert_state_with_diff(
19869        r#"
19870        use some::mod1;
19871        use some::mod2;
19872
19873      - const A: u32 = 42;
19874      - const B: u32 = 42;
19875      - const C: u32 = 42;
19876        ˇ
19877
19878        fn main() {
19879            println!("hello");
19880
19881            println!("world");
19882        }
19883      "#
19884        .unindent(),
19885    );
19886
19887    cx.update_editor(|editor, window, cx| {
19888        editor.handle_input("replacement", window, cx);
19889    });
19890    executor.run_until_parked();
19891    cx.assert_state_with_diff(
19892        r#"
19893        use some::mod1;
19894        use some::mod2;
19895
19896      - const A: u32 = 42;
19897      - const B: u32 = 42;
19898      - const C: u32 = 42;
19899      -
19900      + replacementˇ
19901
19902        fn main() {
19903            println!("hello");
19904
19905            println!("world");
19906        }
19907      "#
19908        .unindent(),
19909    );
19910}
19911
19912#[gpui::test]
19913async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19914    init_test(cx, |_| {});
19915
19916    let mut cx = EditorTestContext::new(cx).await;
19917
19918    let base_text = r#"
19919        one
19920        two
19921        three
19922        four
19923        five
19924    "#
19925    .unindent();
19926    executor.run_until_parked();
19927    cx.set_state(
19928        &r#"
19929        one
19930        two
19931        fˇour
19932        five
19933        "#
19934        .unindent(),
19935    );
19936
19937    cx.set_head_text(&base_text);
19938    executor.run_until_parked();
19939
19940    cx.update_editor(|editor, window, cx| {
19941        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19942    });
19943    executor.run_until_parked();
19944
19945    cx.assert_state_with_diff(
19946        r#"
19947          one
19948          two
19949        - three
19950          fˇour
19951          five
19952        "#
19953        .unindent(),
19954    );
19955
19956    cx.update_editor(|editor, window, cx| {
19957        editor.backspace(&Backspace, window, cx);
19958        editor.backspace(&Backspace, window, cx);
19959    });
19960    executor.run_until_parked();
19961    cx.assert_state_with_diff(
19962        r#"
19963          one
19964          two
19965        - threeˇ
19966        - four
19967        + our
19968          five
19969        "#
19970        .unindent(),
19971    );
19972}
19973
19974#[gpui::test]
19975async fn test_edit_after_expanded_modification_hunk(
19976    executor: BackgroundExecutor,
19977    cx: &mut TestAppContext,
19978) {
19979    init_test(cx, |_| {});
19980
19981    let mut cx = EditorTestContext::new(cx).await;
19982
19983    let diff_base = r#"
19984        use some::mod1;
19985        use some::mod2;
19986
19987        const A: u32 = 42;
19988        const B: u32 = 42;
19989        const C: u32 = 42;
19990        const D: u32 = 42;
19991
19992
19993        fn main() {
19994            println!("hello");
19995
19996            println!("world");
19997        }"#
19998    .unindent();
19999
20000    cx.set_state(
20001        &r#"
20002        use some::mod1;
20003        use some::mod2;
20004
20005        const A: u32 = 42;
20006        const B: u32 = 42;
20007        const C: u32 = 43ˇ
20008        const D: u32 = 42;
20009
20010
20011        fn main() {
20012            println!("hello");
20013
20014            println!("world");
20015        }"#
20016        .unindent(),
20017    );
20018
20019    cx.set_head_text(&diff_base);
20020    executor.run_until_parked();
20021    cx.update_editor(|editor, window, cx| {
20022        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20023    });
20024    executor.run_until_parked();
20025
20026    cx.assert_state_with_diff(
20027        r#"
20028        use some::mod1;
20029        use some::mod2;
20030
20031        const A: u32 = 42;
20032        const B: u32 = 42;
20033      - const C: u32 = 42;
20034      + const C: u32 = 43ˇ
20035        const D: u32 = 42;
20036
20037
20038        fn main() {
20039            println!("hello");
20040
20041            println!("world");
20042        }"#
20043        .unindent(),
20044    );
20045
20046    cx.update_editor(|editor, window, cx| {
20047        editor.handle_input("\nnew_line\n", window, cx);
20048    });
20049    executor.run_until_parked();
20050
20051    cx.assert_state_with_diff(
20052        r#"
20053        use some::mod1;
20054        use some::mod2;
20055
20056        const A: u32 = 42;
20057        const B: u32 = 42;
20058      - const C: u32 = 42;
20059      + const C: u32 = 43
20060      + new_line
20061      + ˇ
20062        const D: u32 = 42;
20063
20064
20065        fn main() {
20066            println!("hello");
20067
20068            println!("world");
20069        }"#
20070        .unindent(),
20071    );
20072}
20073
20074#[gpui::test]
20075async fn test_stage_and_unstage_added_file_hunk(
20076    executor: BackgroundExecutor,
20077    cx: &mut TestAppContext,
20078) {
20079    init_test(cx, |_| {});
20080
20081    let mut cx = EditorTestContext::new(cx).await;
20082    cx.update_editor(|editor, _, cx| {
20083        editor.set_expand_all_diff_hunks(cx);
20084    });
20085
20086    let working_copy = r#"
20087            ˇfn main() {
20088                println!("hello, world!");
20089            }
20090        "#
20091    .unindent();
20092
20093    cx.set_state(&working_copy);
20094    executor.run_until_parked();
20095
20096    cx.assert_state_with_diff(
20097        r#"
20098            + ˇfn main() {
20099            +     println!("hello, world!");
20100            + }
20101        "#
20102        .unindent(),
20103    );
20104    cx.assert_index_text(None);
20105
20106    cx.update_editor(|editor, window, cx| {
20107        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20108    });
20109    executor.run_until_parked();
20110    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20111    cx.assert_state_with_diff(
20112        r#"
20113            + ˇfn main() {
20114            +     println!("hello, world!");
20115            + }
20116        "#
20117        .unindent(),
20118    );
20119
20120    cx.update_editor(|editor, window, cx| {
20121        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20122    });
20123    executor.run_until_parked();
20124    cx.assert_index_text(None);
20125}
20126
20127async fn setup_indent_guides_editor(
20128    text: &str,
20129    cx: &mut TestAppContext,
20130) -> (BufferId, EditorTestContext) {
20131    init_test(cx, |_| {});
20132
20133    let mut cx = EditorTestContext::new(cx).await;
20134
20135    let buffer_id = cx.update_editor(|editor, window, cx| {
20136        editor.set_text(text, window, cx);
20137        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20138
20139        buffer_ids[0]
20140    });
20141
20142    (buffer_id, cx)
20143}
20144
20145fn assert_indent_guides(
20146    range: Range<u32>,
20147    expected: Vec<IndentGuide>,
20148    active_indices: Option<Vec<usize>>,
20149    cx: &mut EditorTestContext,
20150) {
20151    let indent_guides = cx.update_editor(|editor, window, cx| {
20152        let snapshot = editor.snapshot(window, cx).display_snapshot;
20153        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20154            editor,
20155            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20156            true,
20157            &snapshot,
20158            cx,
20159        );
20160
20161        indent_guides.sort_by(|a, b| {
20162            a.depth.cmp(&b.depth).then(
20163                a.start_row
20164                    .cmp(&b.start_row)
20165                    .then(a.end_row.cmp(&b.end_row)),
20166            )
20167        });
20168        indent_guides
20169    });
20170
20171    if let Some(expected) = active_indices {
20172        let active_indices = cx.update_editor(|editor, window, cx| {
20173            let snapshot = editor.snapshot(window, cx).display_snapshot;
20174            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20175        });
20176
20177        assert_eq!(
20178            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20179            expected,
20180            "Active indent guide indices do not match"
20181        );
20182    }
20183
20184    assert_eq!(indent_guides, expected, "Indent guides do not match");
20185}
20186
20187fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20188    IndentGuide {
20189        buffer_id,
20190        start_row: MultiBufferRow(start_row),
20191        end_row: MultiBufferRow(end_row),
20192        depth,
20193        tab_size: 4,
20194        settings: IndentGuideSettings {
20195            enabled: true,
20196            line_width: 1,
20197            active_line_width: 1,
20198            coloring: IndentGuideColoring::default(),
20199            background_coloring: IndentGuideBackgroundColoring::default(),
20200        },
20201    }
20202}
20203
20204#[gpui::test]
20205async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20206    let (buffer_id, mut cx) = setup_indent_guides_editor(
20207        &"
20208        fn main() {
20209            let a = 1;
20210        }"
20211        .unindent(),
20212        cx,
20213    )
20214    .await;
20215
20216    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20217}
20218
20219#[gpui::test]
20220async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20221    let (buffer_id, mut cx) = setup_indent_guides_editor(
20222        &"
20223        fn main() {
20224            let a = 1;
20225            let b = 2;
20226        }"
20227        .unindent(),
20228        cx,
20229    )
20230    .await;
20231
20232    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20233}
20234
20235#[gpui::test]
20236async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20237    let (buffer_id, mut cx) = setup_indent_guides_editor(
20238        &"
20239        fn main() {
20240            let a = 1;
20241            if a == 3 {
20242                let b = 2;
20243            } else {
20244                let c = 3;
20245            }
20246        }"
20247        .unindent(),
20248        cx,
20249    )
20250    .await;
20251
20252    assert_indent_guides(
20253        0..8,
20254        vec![
20255            indent_guide(buffer_id, 1, 6, 0),
20256            indent_guide(buffer_id, 3, 3, 1),
20257            indent_guide(buffer_id, 5, 5, 1),
20258        ],
20259        None,
20260        &mut cx,
20261    );
20262}
20263
20264#[gpui::test]
20265async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20266    let (buffer_id, mut cx) = setup_indent_guides_editor(
20267        &"
20268        fn main() {
20269            let a = 1;
20270                let b = 2;
20271            let c = 3;
20272        }"
20273        .unindent(),
20274        cx,
20275    )
20276    .await;
20277
20278    assert_indent_guides(
20279        0..5,
20280        vec![
20281            indent_guide(buffer_id, 1, 3, 0),
20282            indent_guide(buffer_id, 2, 2, 1),
20283        ],
20284        None,
20285        &mut cx,
20286    );
20287}
20288
20289#[gpui::test]
20290async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20291    let (buffer_id, mut cx) = setup_indent_guides_editor(
20292        &"
20293        fn main() {
20294            let a = 1;
20295
20296            let c = 3;
20297        }"
20298        .unindent(),
20299        cx,
20300    )
20301    .await;
20302
20303    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20304}
20305
20306#[gpui::test]
20307async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20308    let (buffer_id, mut cx) = setup_indent_guides_editor(
20309        &"
20310        fn main() {
20311            let a = 1;
20312
20313            let c = 3;
20314
20315            if a == 3 {
20316                let b = 2;
20317            } else {
20318                let c = 3;
20319            }
20320        }"
20321        .unindent(),
20322        cx,
20323    )
20324    .await;
20325
20326    assert_indent_guides(
20327        0..11,
20328        vec![
20329            indent_guide(buffer_id, 1, 9, 0),
20330            indent_guide(buffer_id, 6, 6, 1),
20331            indent_guide(buffer_id, 8, 8, 1),
20332        ],
20333        None,
20334        &mut cx,
20335    );
20336}
20337
20338#[gpui::test]
20339async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20340    let (buffer_id, mut cx) = setup_indent_guides_editor(
20341        &"
20342        fn main() {
20343            let a = 1;
20344
20345            let c = 3;
20346
20347            if a == 3 {
20348                let b = 2;
20349            } else {
20350                let c = 3;
20351            }
20352        }"
20353        .unindent(),
20354        cx,
20355    )
20356    .await;
20357
20358    assert_indent_guides(
20359        1..11,
20360        vec![
20361            indent_guide(buffer_id, 1, 9, 0),
20362            indent_guide(buffer_id, 6, 6, 1),
20363            indent_guide(buffer_id, 8, 8, 1),
20364        ],
20365        None,
20366        &mut cx,
20367    );
20368}
20369
20370#[gpui::test]
20371async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20372    let (buffer_id, mut cx) = setup_indent_guides_editor(
20373        &"
20374        fn main() {
20375            let a = 1;
20376
20377            let c = 3;
20378
20379            if a == 3 {
20380                let b = 2;
20381            } else {
20382                let c = 3;
20383            }
20384        }"
20385        .unindent(),
20386        cx,
20387    )
20388    .await;
20389
20390    assert_indent_guides(
20391        1..10,
20392        vec![
20393            indent_guide(buffer_id, 1, 9, 0),
20394            indent_guide(buffer_id, 6, 6, 1),
20395            indent_guide(buffer_id, 8, 8, 1),
20396        ],
20397        None,
20398        &mut cx,
20399    );
20400}
20401
20402#[gpui::test]
20403async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20404    let (buffer_id, mut cx) = setup_indent_guides_editor(
20405        &"
20406        fn main() {
20407            if a {
20408                b(
20409                    c,
20410                    d,
20411                )
20412            } else {
20413                e(
20414                    f
20415                )
20416            }
20417        }"
20418        .unindent(),
20419        cx,
20420    )
20421    .await;
20422
20423    assert_indent_guides(
20424        0..11,
20425        vec![
20426            indent_guide(buffer_id, 1, 10, 0),
20427            indent_guide(buffer_id, 2, 5, 1),
20428            indent_guide(buffer_id, 7, 9, 1),
20429            indent_guide(buffer_id, 3, 4, 2),
20430            indent_guide(buffer_id, 8, 8, 2),
20431        ],
20432        None,
20433        &mut cx,
20434    );
20435
20436    cx.update_editor(|editor, window, cx| {
20437        editor.fold_at(MultiBufferRow(2), window, cx);
20438        assert_eq!(
20439            editor.display_text(cx),
20440            "
20441            fn main() {
20442                if a {
20443                    b(⋯
20444                    )
20445                } else {
20446                    e(
20447                        f
20448                    )
20449                }
20450            }"
20451            .unindent()
20452        );
20453    });
20454
20455    assert_indent_guides(
20456        0..11,
20457        vec![
20458            indent_guide(buffer_id, 1, 10, 0),
20459            indent_guide(buffer_id, 2, 5, 1),
20460            indent_guide(buffer_id, 7, 9, 1),
20461            indent_guide(buffer_id, 8, 8, 2),
20462        ],
20463        None,
20464        &mut cx,
20465    );
20466}
20467
20468#[gpui::test]
20469async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20470    let (buffer_id, mut cx) = setup_indent_guides_editor(
20471        &"
20472        block1
20473            block2
20474                block3
20475                    block4
20476            block2
20477        block1
20478        block1"
20479            .unindent(),
20480        cx,
20481    )
20482    .await;
20483
20484    assert_indent_guides(
20485        1..10,
20486        vec![
20487            indent_guide(buffer_id, 1, 4, 0),
20488            indent_guide(buffer_id, 2, 3, 1),
20489            indent_guide(buffer_id, 3, 3, 2),
20490        ],
20491        None,
20492        &mut cx,
20493    );
20494}
20495
20496#[gpui::test]
20497async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20498    let (buffer_id, mut cx) = setup_indent_guides_editor(
20499        &"
20500        block1
20501            block2
20502                block3
20503
20504        block1
20505        block1"
20506            .unindent(),
20507        cx,
20508    )
20509    .await;
20510
20511    assert_indent_guides(
20512        0..6,
20513        vec![
20514            indent_guide(buffer_id, 1, 2, 0),
20515            indent_guide(buffer_id, 2, 2, 1),
20516        ],
20517        None,
20518        &mut cx,
20519    );
20520}
20521
20522#[gpui::test]
20523async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20524    let (buffer_id, mut cx) = setup_indent_guides_editor(
20525        &"
20526        function component() {
20527        \treturn (
20528        \t\t\t
20529        \t\t<div>
20530        \t\t\t<abc></abc>
20531        \t\t</div>
20532        \t)
20533        }"
20534        .unindent(),
20535        cx,
20536    )
20537    .await;
20538
20539    assert_indent_guides(
20540        0..8,
20541        vec![
20542            indent_guide(buffer_id, 1, 6, 0),
20543            indent_guide(buffer_id, 2, 5, 1),
20544            indent_guide(buffer_id, 4, 4, 2),
20545        ],
20546        None,
20547        &mut cx,
20548    );
20549}
20550
20551#[gpui::test]
20552async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20553    let (buffer_id, mut cx) = setup_indent_guides_editor(
20554        &"
20555        function component() {
20556        \treturn (
20557        \t
20558        \t\t<div>
20559        \t\t\t<abc></abc>
20560        \t\t</div>
20561        \t)
20562        }"
20563        .unindent(),
20564        cx,
20565    )
20566    .await;
20567
20568    assert_indent_guides(
20569        0..8,
20570        vec![
20571            indent_guide(buffer_id, 1, 6, 0),
20572            indent_guide(buffer_id, 2, 5, 1),
20573            indent_guide(buffer_id, 4, 4, 2),
20574        ],
20575        None,
20576        &mut cx,
20577    );
20578}
20579
20580#[gpui::test]
20581async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20582    let (buffer_id, mut cx) = setup_indent_guides_editor(
20583        &"
20584        block1
20585
20586
20587
20588            block2
20589        "
20590        .unindent(),
20591        cx,
20592    )
20593    .await;
20594
20595    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20596}
20597
20598#[gpui::test]
20599async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20600    let (buffer_id, mut cx) = setup_indent_guides_editor(
20601        &"
20602        def a:
20603        \tb = 3
20604        \tif True:
20605        \t\tc = 4
20606        \t\td = 5
20607        \tprint(b)
20608        "
20609        .unindent(),
20610        cx,
20611    )
20612    .await;
20613
20614    assert_indent_guides(
20615        0..6,
20616        vec![
20617            indent_guide(buffer_id, 1, 5, 0),
20618            indent_guide(buffer_id, 3, 4, 1),
20619        ],
20620        None,
20621        &mut cx,
20622    );
20623}
20624
20625#[gpui::test]
20626async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20627    let (buffer_id, mut cx) = setup_indent_guides_editor(
20628        &"
20629    fn main() {
20630        let a = 1;
20631    }"
20632        .unindent(),
20633        cx,
20634    )
20635    .await;
20636
20637    cx.update_editor(|editor, window, cx| {
20638        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20639            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20640        });
20641    });
20642
20643    assert_indent_guides(
20644        0..3,
20645        vec![indent_guide(buffer_id, 1, 1, 0)],
20646        Some(vec![0]),
20647        &mut cx,
20648    );
20649}
20650
20651#[gpui::test]
20652async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20653    let (buffer_id, mut cx) = setup_indent_guides_editor(
20654        &"
20655    fn main() {
20656        if 1 == 2 {
20657            let a = 1;
20658        }
20659    }"
20660        .unindent(),
20661        cx,
20662    )
20663    .await;
20664
20665    cx.update_editor(|editor, window, cx| {
20666        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20667            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20668        });
20669    });
20670
20671    assert_indent_guides(
20672        0..4,
20673        vec![
20674            indent_guide(buffer_id, 1, 3, 0),
20675            indent_guide(buffer_id, 2, 2, 1),
20676        ],
20677        Some(vec![1]),
20678        &mut cx,
20679    );
20680
20681    cx.update_editor(|editor, window, cx| {
20682        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20683            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20684        });
20685    });
20686
20687    assert_indent_guides(
20688        0..4,
20689        vec![
20690            indent_guide(buffer_id, 1, 3, 0),
20691            indent_guide(buffer_id, 2, 2, 1),
20692        ],
20693        Some(vec![1]),
20694        &mut cx,
20695    );
20696
20697    cx.update_editor(|editor, window, cx| {
20698        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20699            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20700        });
20701    });
20702
20703    assert_indent_guides(
20704        0..4,
20705        vec![
20706            indent_guide(buffer_id, 1, 3, 0),
20707            indent_guide(buffer_id, 2, 2, 1),
20708        ],
20709        Some(vec![0]),
20710        &mut cx,
20711    );
20712}
20713
20714#[gpui::test]
20715async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20716    let (buffer_id, mut cx) = setup_indent_guides_editor(
20717        &"
20718    fn main() {
20719        let a = 1;
20720
20721        let b = 2;
20722    }"
20723        .unindent(),
20724        cx,
20725    )
20726    .await;
20727
20728    cx.update_editor(|editor, window, cx| {
20729        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20730            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20731        });
20732    });
20733
20734    assert_indent_guides(
20735        0..5,
20736        vec![indent_guide(buffer_id, 1, 3, 0)],
20737        Some(vec![0]),
20738        &mut cx,
20739    );
20740}
20741
20742#[gpui::test]
20743async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20744    let (buffer_id, mut cx) = setup_indent_guides_editor(
20745        &"
20746    def m:
20747        a = 1
20748        pass"
20749            .unindent(),
20750        cx,
20751    )
20752    .await;
20753
20754    cx.update_editor(|editor, window, cx| {
20755        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20756            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20757        });
20758    });
20759
20760    assert_indent_guides(
20761        0..3,
20762        vec![indent_guide(buffer_id, 1, 2, 0)],
20763        Some(vec![0]),
20764        &mut cx,
20765    );
20766}
20767
20768#[gpui::test]
20769async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20770    init_test(cx, |_| {});
20771    let mut cx = EditorTestContext::new(cx).await;
20772    let text = indoc! {
20773        "
20774        impl A {
20775            fn b() {
20776                0;
20777                3;
20778                5;
20779                6;
20780                7;
20781            }
20782        }
20783        "
20784    };
20785    let base_text = indoc! {
20786        "
20787        impl A {
20788            fn b() {
20789                0;
20790                1;
20791                2;
20792                3;
20793                4;
20794            }
20795            fn c() {
20796                5;
20797                6;
20798                7;
20799            }
20800        }
20801        "
20802    };
20803
20804    cx.update_editor(|editor, window, cx| {
20805        editor.set_text(text, window, cx);
20806
20807        editor.buffer().update(cx, |multibuffer, cx| {
20808            let buffer = multibuffer.as_singleton().unwrap();
20809            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20810
20811            multibuffer.set_all_diff_hunks_expanded(cx);
20812            multibuffer.add_diff(diff, cx);
20813
20814            buffer.read(cx).remote_id()
20815        })
20816    });
20817    cx.run_until_parked();
20818
20819    cx.assert_state_with_diff(
20820        indoc! { "
20821          impl A {
20822              fn b() {
20823                  0;
20824        -         1;
20825        -         2;
20826                  3;
20827        -         4;
20828        -     }
20829        -     fn c() {
20830                  5;
20831                  6;
20832                  7;
20833              }
20834          }
20835          ˇ"
20836        }
20837        .to_string(),
20838    );
20839
20840    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20841        editor
20842            .snapshot(window, cx)
20843            .buffer_snapshot()
20844            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20845            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20846            .collect::<Vec<_>>()
20847    });
20848    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20849    assert_eq!(
20850        actual_guides,
20851        vec![
20852            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20853            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20854            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20855        ]
20856    );
20857}
20858
20859#[gpui::test]
20860async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20861    init_test(cx, |_| {});
20862    let mut cx = EditorTestContext::new(cx).await;
20863
20864    let diff_base = r#"
20865        a
20866        b
20867        c
20868        "#
20869    .unindent();
20870
20871    cx.set_state(
20872        &r#"
20873        ˇA
20874        b
20875        C
20876        "#
20877        .unindent(),
20878    );
20879    cx.set_head_text(&diff_base);
20880    cx.update_editor(|editor, window, cx| {
20881        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20882    });
20883    executor.run_until_parked();
20884
20885    let both_hunks_expanded = r#"
20886        - a
20887        + ˇA
20888          b
20889        - c
20890        + C
20891        "#
20892    .unindent();
20893
20894    cx.assert_state_with_diff(both_hunks_expanded.clone());
20895
20896    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20897        let snapshot = editor.snapshot(window, cx);
20898        let hunks = editor
20899            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
20900            .collect::<Vec<_>>();
20901        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20902        let buffer_id = hunks[0].buffer_id;
20903        hunks
20904            .into_iter()
20905            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20906            .collect::<Vec<_>>()
20907    });
20908    assert_eq!(hunk_ranges.len(), 2);
20909
20910    cx.update_editor(|editor, _, cx| {
20911        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20912    });
20913    executor.run_until_parked();
20914
20915    let second_hunk_expanded = r#"
20916          ˇA
20917          b
20918        - c
20919        + C
20920        "#
20921    .unindent();
20922
20923    cx.assert_state_with_diff(second_hunk_expanded);
20924
20925    cx.update_editor(|editor, _, cx| {
20926        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20927    });
20928    executor.run_until_parked();
20929
20930    cx.assert_state_with_diff(both_hunks_expanded.clone());
20931
20932    cx.update_editor(|editor, _, cx| {
20933        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20934    });
20935    executor.run_until_parked();
20936
20937    let first_hunk_expanded = r#"
20938        - a
20939        + ˇA
20940          b
20941          C
20942        "#
20943    .unindent();
20944
20945    cx.assert_state_with_diff(first_hunk_expanded);
20946
20947    cx.update_editor(|editor, _, cx| {
20948        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20949    });
20950    executor.run_until_parked();
20951
20952    cx.assert_state_with_diff(both_hunks_expanded);
20953
20954    cx.set_state(
20955        &r#"
20956        ˇA
20957        b
20958        "#
20959        .unindent(),
20960    );
20961    cx.run_until_parked();
20962
20963    // TODO this cursor position seems bad
20964    cx.assert_state_with_diff(
20965        r#"
20966        - ˇa
20967        + A
20968          b
20969        "#
20970        .unindent(),
20971    );
20972
20973    cx.update_editor(|editor, window, cx| {
20974        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20975    });
20976
20977    cx.assert_state_with_diff(
20978        r#"
20979            - ˇa
20980            + A
20981              b
20982            - c
20983            "#
20984        .unindent(),
20985    );
20986
20987    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20988        let snapshot = editor.snapshot(window, cx);
20989        let hunks = editor
20990            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
20991            .collect::<Vec<_>>();
20992        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20993        let buffer_id = hunks[0].buffer_id;
20994        hunks
20995            .into_iter()
20996            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20997            .collect::<Vec<_>>()
20998    });
20999    assert_eq!(hunk_ranges.len(), 2);
21000
21001    cx.update_editor(|editor, _, cx| {
21002        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21003    });
21004    executor.run_until_parked();
21005
21006    cx.assert_state_with_diff(
21007        r#"
21008        - ˇa
21009        + A
21010          b
21011        "#
21012        .unindent(),
21013    );
21014}
21015
21016#[gpui::test]
21017async fn test_toggle_deletion_hunk_at_start_of_file(
21018    executor: BackgroundExecutor,
21019    cx: &mut TestAppContext,
21020) {
21021    init_test(cx, |_| {});
21022    let mut cx = EditorTestContext::new(cx).await;
21023
21024    let diff_base = r#"
21025        a
21026        b
21027        c
21028        "#
21029    .unindent();
21030
21031    cx.set_state(
21032        &r#"
21033        ˇb
21034        c
21035        "#
21036        .unindent(),
21037    );
21038    cx.set_head_text(&diff_base);
21039    cx.update_editor(|editor, window, cx| {
21040        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21041    });
21042    executor.run_until_parked();
21043
21044    let hunk_expanded = r#"
21045        - a
21046          ˇb
21047          c
21048        "#
21049    .unindent();
21050
21051    cx.assert_state_with_diff(hunk_expanded.clone());
21052
21053    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21054        let snapshot = editor.snapshot(window, cx);
21055        let hunks = editor
21056            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21057            .collect::<Vec<_>>();
21058        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21059        let buffer_id = hunks[0].buffer_id;
21060        hunks
21061            .into_iter()
21062            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21063            .collect::<Vec<_>>()
21064    });
21065    assert_eq!(hunk_ranges.len(), 1);
21066
21067    cx.update_editor(|editor, _, cx| {
21068        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21069    });
21070    executor.run_until_parked();
21071
21072    let hunk_collapsed = r#"
21073          ˇb
21074          c
21075        "#
21076    .unindent();
21077
21078    cx.assert_state_with_diff(hunk_collapsed);
21079
21080    cx.update_editor(|editor, _, cx| {
21081        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21082    });
21083    executor.run_until_parked();
21084
21085    cx.assert_state_with_diff(hunk_expanded);
21086}
21087
21088#[gpui::test]
21089async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21090    init_test(cx, |_| {});
21091
21092    let fs = FakeFs::new(cx.executor());
21093    fs.insert_tree(
21094        path!("/test"),
21095        json!({
21096            ".git": {},
21097            "file-1": "ONE\n",
21098            "file-2": "TWO\n",
21099            "file-3": "THREE\n",
21100        }),
21101    )
21102    .await;
21103
21104    fs.set_head_for_repo(
21105        path!("/test/.git").as_ref(),
21106        &[
21107            ("file-1", "one\n".into()),
21108            ("file-2", "two\n".into()),
21109            ("file-3", "three\n".into()),
21110        ],
21111        "deadbeef",
21112    );
21113
21114    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21115    let mut buffers = vec![];
21116    for i in 1..=3 {
21117        let buffer = project
21118            .update(cx, |project, cx| {
21119                let path = format!(path!("/test/file-{}"), i);
21120                project.open_local_buffer(path, cx)
21121            })
21122            .await
21123            .unwrap();
21124        buffers.push(buffer);
21125    }
21126
21127    let multibuffer = cx.new(|cx| {
21128        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21129        multibuffer.set_all_diff_hunks_expanded(cx);
21130        for buffer in &buffers {
21131            let snapshot = buffer.read(cx).snapshot();
21132            multibuffer.set_excerpts_for_path(
21133                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21134                buffer.clone(),
21135                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21136                2,
21137                cx,
21138            );
21139        }
21140        multibuffer
21141    });
21142
21143    let editor = cx.add_window(|window, cx| {
21144        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21145    });
21146    cx.run_until_parked();
21147
21148    let snapshot = editor
21149        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21150        .unwrap();
21151    let hunks = snapshot
21152        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21153        .map(|hunk| match hunk {
21154            DisplayDiffHunk::Unfolded {
21155                display_row_range, ..
21156            } => display_row_range,
21157            DisplayDiffHunk::Folded { .. } => unreachable!(),
21158        })
21159        .collect::<Vec<_>>();
21160    assert_eq!(
21161        hunks,
21162        [
21163            DisplayRow(2)..DisplayRow(4),
21164            DisplayRow(7)..DisplayRow(9),
21165            DisplayRow(12)..DisplayRow(14),
21166        ]
21167    );
21168}
21169
21170#[gpui::test]
21171async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21172    init_test(cx, |_| {});
21173
21174    let mut cx = EditorTestContext::new(cx).await;
21175    cx.set_head_text(indoc! { "
21176        one
21177        two
21178        three
21179        four
21180        five
21181        "
21182    });
21183    cx.set_index_text(indoc! { "
21184        one
21185        two
21186        three
21187        four
21188        five
21189        "
21190    });
21191    cx.set_state(indoc! {"
21192        one
21193        TWO
21194        ˇTHREE
21195        FOUR
21196        five
21197    "});
21198    cx.run_until_parked();
21199    cx.update_editor(|editor, window, cx| {
21200        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21201    });
21202    cx.run_until_parked();
21203    cx.assert_index_text(Some(indoc! {"
21204        one
21205        TWO
21206        THREE
21207        FOUR
21208        five
21209    "}));
21210    cx.set_state(indoc! { "
21211        one
21212        TWO
21213        ˇTHREE-HUNDRED
21214        FOUR
21215        five
21216    "});
21217    cx.run_until_parked();
21218    cx.update_editor(|editor, window, cx| {
21219        let snapshot = editor.snapshot(window, cx);
21220        let hunks = editor
21221            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21222            .collect::<Vec<_>>();
21223        assert_eq!(hunks.len(), 1);
21224        assert_eq!(
21225            hunks[0].status(),
21226            DiffHunkStatus {
21227                kind: DiffHunkStatusKind::Modified,
21228                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21229            }
21230        );
21231
21232        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21233    });
21234    cx.run_until_parked();
21235    cx.assert_index_text(Some(indoc! {"
21236        one
21237        TWO
21238        THREE-HUNDRED
21239        FOUR
21240        five
21241    "}));
21242}
21243
21244#[gpui::test]
21245fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21246    init_test(cx, |_| {});
21247
21248    let editor = cx.add_window(|window, cx| {
21249        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21250        build_editor(buffer, window, cx)
21251    });
21252
21253    let render_args = Arc::new(Mutex::new(None));
21254    let snapshot = editor
21255        .update(cx, |editor, window, cx| {
21256            let snapshot = editor.buffer().read(cx).snapshot(cx);
21257            let range =
21258                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21259
21260            struct RenderArgs {
21261                row: MultiBufferRow,
21262                folded: bool,
21263                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21264            }
21265
21266            let crease = Crease::inline(
21267                range,
21268                FoldPlaceholder::test(),
21269                {
21270                    let toggle_callback = render_args.clone();
21271                    move |row, folded, callback, _window, _cx| {
21272                        *toggle_callback.lock() = Some(RenderArgs {
21273                            row,
21274                            folded,
21275                            callback,
21276                        });
21277                        div()
21278                    }
21279                },
21280                |_row, _folded, _window, _cx| div(),
21281            );
21282
21283            editor.insert_creases(Some(crease), cx);
21284            let snapshot = editor.snapshot(window, cx);
21285            let _div =
21286                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21287            snapshot
21288        })
21289        .unwrap();
21290
21291    let render_args = render_args.lock().take().unwrap();
21292    assert_eq!(render_args.row, MultiBufferRow(1));
21293    assert!(!render_args.folded);
21294    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21295
21296    cx.update_window(*editor, |_, window, cx| {
21297        (render_args.callback)(true, window, cx)
21298    })
21299    .unwrap();
21300    let snapshot = editor
21301        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21302        .unwrap();
21303    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21304
21305    cx.update_window(*editor, |_, window, cx| {
21306        (render_args.callback)(false, window, cx)
21307    })
21308    .unwrap();
21309    let snapshot = editor
21310        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21311        .unwrap();
21312    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21313}
21314
21315#[gpui::test]
21316async fn test_input_text(cx: &mut TestAppContext) {
21317    init_test(cx, |_| {});
21318    let mut cx = EditorTestContext::new(cx).await;
21319
21320    cx.set_state(
21321        &r#"ˇone
21322        two
21323
21324        three
21325        fourˇ
21326        five
21327
21328        siˇx"#
21329            .unindent(),
21330    );
21331
21332    cx.dispatch_action(HandleInput(String::new()));
21333    cx.assert_editor_state(
21334        &r#"ˇone
21335        two
21336
21337        three
21338        fourˇ
21339        five
21340
21341        siˇx"#
21342            .unindent(),
21343    );
21344
21345    cx.dispatch_action(HandleInput("AAAA".to_string()));
21346    cx.assert_editor_state(
21347        &r#"AAAAˇone
21348        two
21349
21350        three
21351        fourAAAAˇ
21352        five
21353
21354        siAAAAˇx"#
21355            .unindent(),
21356    );
21357}
21358
21359#[gpui::test]
21360async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21361    init_test(cx, |_| {});
21362
21363    let mut cx = EditorTestContext::new(cx).await;
21364    cx.set_state(
21365        r#"let foo = 1;
21366let foo = 2;
21367let foo = 3;
21368let fooˇ = 4;
21369let foo = 5;
21370let foo = 6;
21371let foo = 7;
21372let foo = 8;
21373let foo = 9;
21374let foo = 10;
21375let foo = 11;
21376let foo = 12;
21377let foo = 13;
21378let foo = 14;
21379let foo = 15;"#,
21380    );
21381
21382    cx.update_editor(|e, window, cx| {
21383        assert_eq!(
21384            e.next_scroll_position,
21385            NextScrollCursorCenterTopBottom::Center,
21386            "Default next scroll direction is center",
21387        );
21388
21389        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21390        assert_eq!(
21391            e.next_scroll_position,
21392            NextScrollCursorCenterTopBottom::Top,
21393            "After center, next scroll direction should be top",
21394        );
21395
21396        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21397        assert_eq!(
21398            e.next_scroll_position,
21399            NextScrollCursorCenterTopBottom::Bottom,
21400            "After top, next scroll direction should be bottom",
21401        );
21402
21403        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21404        assert_eq!(
21405            e.next_scroll_position,
21406            NextScrollCursorCenterTopBottom::Center,
21407            "After bottom, scrolling should start over",
21408        );
21409
21410        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21411        assert_eq!(
21412            e.next_scroll_position,
21413            NextScrollCursorCenterTopBottom::Top,
21414            "Scrolling continues if retriggered fast enough"
21415        );
21416    });
21417
21418    cx.executor()
21419        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21420    cx.executor().run_until_parked();
21421    cx.update_editor(|e, _, _| {
21422        assert_eq!(
21423            e.next_scroll_position,
21424            NextScrollCursorCenterTopBottom::Center,
21425            "If scrolling is not triggered fast enough, it should reset"
21426        );
21427    });
21428}
21429
21430#[gpui::test]
21431async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21432    init_test(cx, |_| {});
21433    let mut cx = EditorLspTestContext::new_rust(
21434        lsp::ServerCapabilities {
21435            definition_provider: Some(lsp::OneOf::Left(true)),
21436            references_provider: Some(lsp::OneOf::Left(true)),
21437            ..lsp::ServerCapabilities::default()
21438        },
21439        cx,
21440    )
21441    .await;
21442
21443    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21444        let go_to_definition = cx
21445            .lsp
21446            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21447                move |params, _| async move {
21448                    if empty_go_to_definition {
21449                        Ok(None)
21450                    } else {
21451                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21452                            uri: params.text_document_position_params.text_document.uri,
21453                            range: lsp::Range::new(
21454                                lsp::Position::new(4, 3),
21455                                lsp::Position::new(4, 6),
21456                            ),
21457                        })))
21458                    }
21459                },
21460            );
21461        let references = cx
21462            .lsp
21463            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21464                Ok(Some(vec![lsp::Location {
21465                    uri: params.text_document_position.text_document.uri,
21466                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21467                }]))
21468            });
21469        (go_to_definition, references)
21470    };
21471
21472    cx.set_state(
21473        &r#"fn one() {
21474            let mut a = ˇtwo();
21475        }
21476
21477        fn two() {}"#
21478            .unindent(),
21479    );
21480    set_up_lsp_handlers(false, &mut cx);
21481    let navigated = cx
21482        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21483        .await
21484        .expect("Failed to navigate to definition");
21485    assert_eq!(
21486        navigated,
21487        Navigated::Yes,
21488        "Should have navigated to definition from the GetDefinition response"
21489    );
21490    cx.assert_editor_state(
21491        &r#"fn one() {
21492            let mut a = two();
21493        }
21494
21495        fn «twoˇ»() {}"#
21496            .unindent(),
21497    );
21498
21499    let editors = cx.update_workspace(|workspace, _, cx| {
21500        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21501    });
21502    cx.update_editor(|_, _, test_editor_cx| {
21503        assert_eq!(
21504            editors.len(),
21505            1,
21506            "Initially, only one, test, editor should be open in the workspace"
21507        );
21508        assert_eq!(
21509            test_editor_cx.entity(),
21510            editors.last().expect("Asserted len is 1").clone()
21511        );
21512    });
21513
21514    set_up_lsp_handlers(true, &mut cx);
21515    let navigated = cx
21516        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21517        .await
21518        .expect("Failed to navigate to lookup references");
21519    assert_eq!(
21520        navigated,
21521        Navigated::Yes,
21522        "Should have navigated to references as a fallback after empty GoToDefinition response"
21523    );
21524    // We should not change the selections in the existing file,
21525    // if opening another milti buffer with the references
21526    cx.assert_editor_state(
21527        &r#"fn one() {
21528            let mut a = two();
21529        }
21530
21531        fn «twoˇ»() {}"#
21532            .unindent(),
21533    );
21534    let editors = cx.update_workspace(|workspace, _, cx| {
21535        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21536    });
21537    cx.update_editor(|_, _, test_editor_cx| {
21538        assert_eq!(
21539            editors.len(),
21540            2,
21541            "After falling back to references search, we open a new editor with the results"
21542        );
21543        let references_fallback_text = editors
21544            .into_iter()
21545            .find(|new_editor| *new_editor != test_editor_cx.entity())
21546            .expect("Should have one non-test editor now")
21547            .read(test_editor_cx)
21548            .text(test_editor_cx);
21549        assert_eq!(
21550            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21551            "Should use the range from the references response and not the GoToDefinition one"
21552        );
21553    });
21554}
21555
21556#[gpui::test]
21557async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21558    init_test(cx, |_| {});
21559    cx.update(|cx| {
21560        let mut editor_settings = EditorSettings::get_global(cx).clone();
21561        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21562        EditorSettings::override_global(editor_settings, cx);
21563    });
21564    let mut cx = EditorLspTestContext::new_rust(
21565        lsp::ServerCapabilities {
21566            definition_provider: Some(lsp::OneOf::Left(true)),
21567            references_provider: Some(lsp::OneOf::Left(true)),
21568            ..lsp::ServerCapabilities::default()
21569        },
21570        cx,
21571    )
21572    .await;
21573    let original_state = r#"fn one() {
21574        let mut a = ˇtwo();
21575    }
21576
21577    fn two() {}"#
21578        .unindent();
21579    cx.set_state(&original_state);
21580
21581    let mut go_to_definition = cx
21582        .lsp
21583        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21584            move |_, _| async move { Ok(None) },
21585        );
21586    let _references = cx
21587        .lsp
21588        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21589            panic!("Should not call for references with no go to definition fallback")
21590        });
21591
21592    let navigated = cx
21593        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21594        .await
21595        .expect("Failed to navigate to lookup references");
21596    go_to_definition
21597        .next()
21598        .await
21599        .expect("Should have called the go_to_definition handler");
21600
21601    assert_eq!(
21602        navigated,
21603        Navigated::No,
21604        "Should have navigated to references as a fallback after empty GoToDefinition response"
21605    );
21606    cx.assert_editor_state(&original_state);
21607    let editors = cx.update_workspace(|workspace, _, cx| {
21608        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21609    });
21610    cx.update_editor(|_, _, _| {
21611        assert_eq!(
21612            editors.len(),
21613            1,
21614            "After unsuccessful fallback, no other editor should have been opened"
21615        );
21616    });
21617}
21618
21619#[gpui::test]
21620async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21621    init_test(cx, |_| {});
21622    let mut cx = EditorLspTestContext::new_rust(
21623        lsp::ServerCapabilities {
21624            references_provider: Some(lsp::OneOf::Left(true)),
21625            ..lsp::ServerCapabilities::default()
21626        },
21627        cx,
21628    )
21629    .await;
21630
21631    cx.set_state(
21632        &r#"
21633        fn one() {
21634            let mut a = two();
21635        }
21636
21637        fn ˇtwo() {}"#
21638            .unindent(),
21639    );
21640    cx.lsp
21641        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21642            Ok(Some(vec![
21643                lsp::Location {
21644                    uri: params.text_document_position.text_document.uri.clone(),
21645                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21646                },
21647                lsp::Location {
21648                    uri: params.text_document_position.text_document.uri,
21649                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21650                },
21651            ]))
21652        });
21653    let navigated = cx
21654        .update_editor(|editor, window, cx| {
21655            editor.find_all_references(&FindAllReferences, window, cx)
21656        })
21657        .unwrap()
21658        .await
21659        .expect("Failed to navigate to references");
21660    assert_eq!(
21661        navigated,
21662        Navigated::Yes,
21663        "Should have navigated to references from the FindAllReferences response"
21664    );
21665    cx.assert_editor_state(
21666        &r#"fn one() {
21667            let mut a = two();
21668        }
21669
21670        fn ˇtwo() {}"#
21671            .unindent(),
21672    );
21673
21674    let editors = cx.update_workspace(|workspace, _, cx| {
21675        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21676    });
21677    cx.update_editor(|_, _, _| {
21678        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21679    });
21680
21681    cx.set_state(
21682        &r#"fn one() {
21683            let mut a = ˇtwo();
21684        }
21685
21686        fn two() {}"#
21687            .unindent(),
21688    );
21689    let navigated = cx
21690        .update_editor(|editor, window, cx| {
21691            editor.find_all_references(&FindAllReferences, window, cx)
21692        })
21693        .unwrap()
21694        .await
21695        .expect("Failed to navigate to references");
21696    assert_eq!(
21697        navigated,
21698        Navigated::Yes,
21699        "Should have navigated to references from the FindAllReferences response"
21700    );
21701    cx.assert_editor_state(
21702        &r#"fn one() {
21703            let mut a = ˇtwo();
21704        }
21705
21706        fn two() {}"#
21707            .unindent(),
21708    );
21709    let editors = cx.update_workspace(|workspace, _, cx| {
21710        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21711    });
21712    cx.update_editor(|_, _, _| {
21713        assert_eq!(
21714            editors.len(),
21715            2,
21716            "should have re-used the previous multibuffer"
21717        );
21718    });
21719
21720    cx.set_state(
21721        &r#"fn one() {
21722            let mut a = ˇtwo();
21723        }
21724        fn three() {}
21725        fn two() {}"#
21726            .unindent(),
21727    );
21728    cx.lsp
21729        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21730            Ok(Some(vec![
21731                lsp::Location {
21732                    uri: params.text_document_position.text_document.uri.clone(),
21733                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21734                },
21735                lsp::Location {
21736                    uri: params.text_document_position.text_document.uri,
21737                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21738                },
21739            ]))
21740        });
21741    let navigated = cx
21742        .update_editor(|editor, window, cx| {
21743            editor.find_all_references(&FindAllReferences, window, cx)
21744        })
21745        .unwrap()
21746        .await
21747        .expect("Failed to navigate to references");
21748    assert_eq!(
21749        navigated,
21750        Navigated::Yes,
21751        "Should have navigated to references from the FindAllReferences response"
21752    );
21753    cx.assert_editor_state(
21754        &r#"fn one() {
21755                let mut a = ˇtwo();
21756            }
21757            fn three() {}
21758            fn two() {}"#
21759            .unindent(),
21760    );
21761    let editors = cx.update_workspace(|workspace, _, cx| {
21762        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21763    });
21764    cx.update_editor(|_, _, _| {
21765        assert_eq!(
21766            editors.len(),
21767            3,
21768            "should have used a new multibuffer as offsets changed"
21769        );
21770    });
21771}
21772#[gpui::test]
21773async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21774    init_test(cx, |_| {});
21775
21776    let language = Arc::new(Language::new(
21777        LanguageConfig::default(),
21778        Some(tree_sitter_rust::LANGUAGE.into()),
21779    ));
21780
21781    let text = r#"
21782        #[cfg(test)]
21783        mod tests() {
21784            #[test]
21785            fn runnable_1() {
21786                let a = 1;
21787            }
21788
21789            #[test]
21790            fn runnable_2() {
21791                let a = 1;
21792                let b = 2;
21793            }
21794        }
21795    "#
21796    .unindent();
21797
21798    let fs = FakeFs::new(cx.executor());
21799    fs.insert_file("/file.rs", Default::default()).await;
21800
21801    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21802    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21803    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21804    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21805    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21806
21807    let editor = cx.new_window_entity(|window, cx| {
21808        Editor::new(
21809            EditorMode::full(),
21810            multi_buffer,
21811            Some(project.clone()),
21812            window,
21813            cx,
21814        )
21815    });
21816
21817    editor.update_in(cx, |editor, window, cx| {
21818        let snapshot = editor.buffer().read(cx).snapshot(cx);
21819        editor.tasks.insert(
21820            (buffer.read(cx).remote_id(), 3),
21821            RunnableTasks {
21822                templates: vec![],
21823                offset: snapshot.anchor_before(43),
21824                column: 0,
21825                extra_variables: HashMap::default(),
21826                context_range: BufferOffset(43)..BufferOffset(85),
21827            },
21828        );
21829        editor.tasks.insert(
21830            (buffer.read(cx).remote_id(), 8),
21831            RunnableTasks {
21832                templates: vec![],
21833                offset: snapshot.anchor_before(86),
21834                column: 0,
21835                extra_variables: HashMap::default(),
21836                context_range: BufferOffset(86)..BufferOffset(191),
21837            },
21838        );
21839
21840        // Test finding task when cursor is inside function body
21841        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21842            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21843        });
21844        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21845        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21846
21847        // Test finding task when cursor is on function name
21848        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21849            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21850        });
21851        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21852        assert_eq!(row, 8, "Should find task when cursor is on function name");
21853    });
21854}
21855
21856#[gpui::test]
21857async fn test_folding_buffers(cx: &mut TestAppContext) {
21858    init_test(cx, |_| {});
21859
21860    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21861    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21862    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21863
21864    let fs = FakeFs::new(cx.executor());
21865    fs.insert_tree(
21866        path!("/a"),
21867        json!({
21868            "first.rs": sample_text_1,
21869            "second.rs": sample_text_2,
21870            "third.rs": sample_text_3,
21871        }),
21872    )
21873    .await;
21874    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21875    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21876    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21877    let worktree = project.update(cx, |project, cx| {
21878        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21879        assert_eq!(worktrees.len(), 1);
21880        worktrees.pop().unwrap()
21881    });
21882    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21883
21884    let buffer_1 = project
21885        .update(cx, |project, cx| {
21886            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
21887        })
21888        .await
21889        .unwrap();
21890    let buffer_2 = project
21891        .update(cx, |project, cx| {
21892            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
21893        })
21894        .await
21895        .unwrap();
21896    let buffer_3 = project
21897        .update(cx, |project, cx| {
21898            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
21899        })
21900        .await
21901        .unwrap();
21902
21903    let multi_buffer = cx.new(|cx| {
21904        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21905        multi_buffer.push_excerpts(
21906            buffer_1.clone(),
21907            [
21908                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21909                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21910                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21911            ],
21912            cx,
21913        );
21914        multi_buffer.push_excerpts(
21915            buffer_2.clone(),
21916            [
21917                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21918                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21919                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21920            ],
21921            cx,
21922        );
21923        multi_buffer.push_excerpts(
21924            buffer_3.clone(),
21925            [
21926                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21927                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21928                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21929            ],
21930            cx,
21931        );
21932        multi_buffer
21933    });
21934    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21935        Editor::new(
21936            EditorMode::full(),
21937            multi_buffer.clone(),
21938            Some(project.clone()),
21939            window,
21940            cx,
21941        )
21942    });
21943
21944    assert_eq!(
21945        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21946        "\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",
21947    );
21948
21949    multi_buffer_editor.update(cx, |editor, cx| {
21950        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21951    });
21952    assert_eq!(
21953        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21954        "\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",
21955        "After folding the first buffer, its text should not be displayed"
21956    );
21957
21958    multi_buffer_editor.update(cx, |editor, cx| {
21959        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21960    });
21961    assert_eq!(
21962        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21963        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21964        "After folding the second buffer, its text should not be displayed"
21965    );
21966
21967    multi_buffer_editor.update(cx, |editor, cx| {
21968        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21969    });
21970    assert_eq!(
21971        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21972        "\n\n\n\n\n",
21973        "After folding the third buffer, its text should not be displayed"
21974    );
21975
21976    // Emulate selection inside the fold logic, that should work
21977    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21978        editor
21979            .snapshot(window, cx)
21980            .next_line_boundary(Point::new(0, 4));
21981    });
21982
21983    multi_buffer_editor.update(cx, |editor, cx| {
21984        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21985    });
21986    assert_eq!(
21987        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21988        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21989        "After unfolding the second buffer, its text should be displayed"
21990    );
21991
21992    // Typing inside of buffer 1 causes that buffer to be unfolded.
21993    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21994        assert_eq!(
21995            multi_buffer
21996                .read(cx)
21997                .snapshot(cx)
21998                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21999                .collect::<String>(),
22000            "bbbb"
22001        );
22002        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22003            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22004        });
22005        editor.handle_input("B", window, cx);
22006    });
22007
22008    assert_eq!(
22009        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22010        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22011        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22012    );
22013
22014    multi_buffer_editor.update(cx, |editor, cx| {
22015        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22016    });
22017    assert_eq!(
22018        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22019        "\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",
22020        "After unfolding the all buffers, all original text should be displayed"
22021    );
22022}
22023
22024#[gpui::test]
22025async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22026    init_test(cx, |_| {});
22027
22028    let sample_text_1 = "1111\n2222\n3333".to_string();
22029    let sample_text_2 = "4444\n5555\n6666".to_string();
22030    let sample_text_3 = "7777\n8888\n9999".to_string();
22031
22032    let fs = FakeFs::new(cx.executor());
22033    fs.insert_tree(
22034        path!("/a"),
22035        json!({
22036            "first.rs": sample_text_1,
22037            "second.rs": sample_text_2,
22038            "third.rs": sample_text_3,
22039        }),
22040    )
22041    .await;
22042    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22043    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22044    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22045    let worktree = project.update(cx, |project, cx| {
22046        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22047        assert_eq!(worktrees.len(), 1);
22048        worktrees.pop().unwrap()
22049    });
22050    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22051
22052    let buffer_1 = project
22053        .update(cx, |project, cx| {
22054            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22055        })
22056        .await
22057        .unwrap();
22058    let buffer_2 = project
22059        .update(cx, |project, cx| {
22060            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22061        })
22062        .await
22063        .unwrap();
22064    let buffer_3 = project
22065        .update(cx, |project, cx| {
22066            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22067        })
22068        .await
22069        .unwrap();
22070
22071    let multi_buffer = cx.new(|cx| {
22072        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22073        multi_buffer.push_excerpts(
22074            buffer_1.clone(),
22075            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22076            cx,
22077        );
22078        multi_buffer.push_excerpts(
22079            buffer_2.clone(),
22080            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22081            cx,
22082        );
22083        multi_buffer.push_excerpts(
22084            buffer_3.clone(),
22085            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22086            cx,
22087        );
22088        multi_buffer
22089    });
22090
22091    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22092        Editor::new(
22093            EditorMode::full(),
22094            multi_buffer,
22095            Some(project.clone()),
22096            window,
22097            cx,
22098        )
22099    });
22100
22101    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22102    assert_eq!(
22103        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22104        full_text,
22105    );
22106
22107    multi_buffer_editor.update(cx, |editor, cx| {
22108        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22109    });
22110    assert_eq!(
22111        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22112        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22113        "After folding the first buffer, its text should not be displayed"
22114    );
22115
22116    multi_buffer_editor.update(cx, |editor, cx| {
22117        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22118    });
22119
22120    assert_eq!(
22121        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22122        "\n\n\n\n\n\n7777\n8888\n9999",
22123        "After folding the second buffer, its text should not be displayed"
22124    );
22125
22126    multi_buffer_editor.update(cx, |editor, cx| {
22127        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22128    });
22129    assert_eq!(
22130        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22131        "\n\n\n\n\n",
22132        "After folding the third buffer, its text should not be displayed"
22133    );
22134
22135    multi_buffer_editor.update(cx, |editor, cx| {
22136        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22137    });
22138    assert_eq!(
22139        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22140        "\n\n\n\n4444\n5555\n6666\n\n",
22141        "After unfolding the second buffer, its text should be displayed"
22142    );
22143
22144    multi_buffer_editor.update(cx, |editor, cx| {
22145        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22146    });
22147    assert_eq!(
22148        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22149        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22150        "After unfolding the first buffer, its text should be displayed"
22151    );
22152
22153    multi_buffer_editor.update(cx, |editor, cx| {
22154        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22155    });
22156    assert_eq!(
22157        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22158        full_text,
22159        "After unfolding all buffers, all original text should be displayed"
22160    );
22161}
22162
22163#[gpui::test]
22164async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22165    init_test(cx, |_| {});
22166
22167    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22168
22169    let fs = FakeFs::new(cx.executor());
22170    fs.insert_tree(
22171        path!("/a"),
22172        json!({
22173            "main.rs": sample_text,
22174        }),
22175    )
22176    .await;
22177    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22178    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22179    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22180    let worktree = project.update(cx, |project, cx| {
22181        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22182        assert_eq!(worktrees.len(), 1);
22183        worktrees.pop().unwrap()
22184    });
22185    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22186
22187    let buffer_1 = project
22188        .update(cx, |project, cx| {
22189            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22190        })
22191        .await
22192        .unwrap();
22193
22194    let multi_buffer = cx.new(|cx| {
22195        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22196        multi_buffer.push_excerpts(
22197            buffer_1.clone(),
22198            [ExcerptRange::new(
22199                Point::new(0, 0)
22200                    ..Point::new(
22201                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22202                        0,
22203                    ),
22204            )],
22205            cx,
22206        );
22207        multi_buffer
22208    });
22209    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22210        Editor::new(
22211            EditorMode::full(),
22212            multi_buffer,
22213            Some(project.clone()),
22214            window,
22215            cx,
22216        )
22217    });
22218
22219    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22220    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22221        enum TestHighlight {}
22222        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22223        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22224        editor.highlight_text::<TestHighlight>(
22225            vec![highlight_range.clone()],
22226            HighlightStyle::color(Hsla::green()),
22227            cx,
22228        );
22229        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22230            s.select_ranges(Some(highlight_range))
22231        });
22232    });
22233
22234    let full_text = format!("\n\n{sample_text}");
22235    assert_eq!(
22236        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22237        full_text,
22238    );
22239}
22240
22241#[gpui::test]
22242async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22243    init_test(cx, |_| {});
22244    cx.update(|cx| {
22245        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22246            "keymaps/default-linux.json",
22247            cx,
22248        )
22249        .unwrap();
22250        cx.bind_keys(default_key_bindings);
22251    });
22252
22253    let (editor, cx) = cx.add_window_view(|window, cx| {
22254        let multi_buffer = MultiBuffer::build_multi(
22255            [
22256                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22257                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22258                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22259                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22260            ],
22261            cx,
22262        );
22263        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22264
22265        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22266        // fold all but the second buffer, so that we test navigating between two
22267        // adjacent folded buffers, as well as folded buffers at the start and
22268        // end the multibuffer
22269        editor.fold_buffer(buffer_ids[0], cx);
22270        editor.fold_buffer(buffer_ids[2], cx);
22271        editor.fold_buffer(buffer_ids[3], cx);
22272
22273        editor
22274    });
22275    cx.simulate_resize(size(px(1000.), px(1000.)));
22276
22277    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22278    cx.assert_excerpts_with_selections(indoc! {"
22279        [EXCERPT]
22280        ˇ[FOLDED]
22281        [EXCERPT]
22282        a1
22283        b1
22284        [EXCERPT]
22285        [FOLDED]
22286        [EXCERPT]
22287        [FOLDED]
22288        "
22289    });
22290    cx.simulate_keystroke("down");
22291    cx.assert_excerpts_with_selections(indoc! {"
22292        [EXCERPT]
22293        [FOLDED]
22294        [EXCERPT]
22295        ˇa1
22296        b1
22297        [EXCERPT]
22298        [FOLDED]
22299        [EXCERPT]
22300        [FOLDED]
22301        "
22302    });
22303    cx.simulate_keystroke("down");
22304    cx.assert_excerpts_with_selections(indoc! {"
22305        [EXCERPT]
22306        [FOLDED]
22307        [EXCERPT]
22308        a1
22309        ˇb1
22310        [EXCERPT]
22311        [FOLDED]
22312        [EXCERPT]
22313        [FOLDED]
22314        "
22315    });
22316    cx.simulate_keystroke("down");
22317    cx.assert_excerpts_with_selections(indoc! {"
22318        [EXCERPT]
22319        [FOLDED]
22320        [EXCERPT]
22321        a1
22322        b1
22323        ˇ[EXCERPT]
22324        [FOLDED]
22325        [EXCERPT]
22326        [FOLDED]
22327        "
22328    });
22329    cx.simulate_keystroke("down");
22330    cx.assert_excerpts_with_selections(indoc! {"
22331        [EXCERPT]
22332        [FOLDED]
22333        [EXCERPT]
22334        a1
22335        b1
22336        [EXCERPT]
22337        ˇ[FOLDED]
22338        [EXCERPT]
22339        [FOLDED]
22340        "
22341    });
22342    for _ in 0..5 {
22343        cx.simulate_keystroke("down");
22344        cx.assert_excerpts_with_selections(indoc! {"
22345            [EXCERPT]
22346            [FOLDED]
22347            [EXCERPT]
22348            a1
22349            b1
22350            [EXCERPT]
22351            [FOLDED]
22352            [EXCERPT]
22353            ˇ[FOLDED]
22354            "
22355        });
22356    }
22357
22358    cx.simulate_keystroke("up");
22359    cx.assert_excerpts_with_selections(indoc! {"
22360        [EXCERPT]
22361        [FOLDED]
22362        [EXCERPT]
22363        a1
22364        b1
22365        [EXCERPT]
22366        ˇ[FOLDED]
22367        [EXCERPT]
22368        [FOLDED]
22369        "
22370    });
22371    cx.simulate_keystroke("up");
22372    cx.assert_excerpts_with_selections(indoc! {"
22373        [EXCERPT]
22374        [FOLDED]
22375        [EXCERPT]
22376        a1
22377        b1
22378        ˇ[EXCERPT]
22379        [FOLDED]
22380        [EXCERPT]
22381        [FOLDED]
22382        "
22383    });
22384    cx.simulate_keystroke("up");
22385    cx.assert_excerpts_with_selections(indoc! {"
22386        [EXCERPT]
22387        [FOLDED]
22388        [EXCERPT]
22389        a1
22390        ˇb1
22391        [EXCERPT]
22392        [FOLDED]
22393        [EXCERPT]
22394        [FOLDED]
22395        "
22396    });
22397    cx.simulate_keystroke("up");
22398    cx.assert_excerpts_with_selections(indoc! {"
22399        [EXCERPT]
22400        [FOLDED]
22401        [EXCERPT]
22402        ˇa1
22403        b1
22404        [EXCERPT]
22405        [FOLDED]
22406        [EXCERPT]
22407        [FOLDED]
22408        "
22409    });
22410    for _ in 0..5 {
22411        cx.simulate_keystroke("up");
22412        cx.assert_excerpts_with_selections(indoc! {"
22413            [EXCERPT]
22414            ˇ[FOLDED]
22415            [EXCERPT]
22416            a1
22417            b1
22418            [EXCERPT]
22419            [FOLDED]
22420            [EXCERPT]
22421            [FOLDED]
22422            "
22423        });
22424    }
22425}
22426
22427#[gpui::test]
22428async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22429    init_test(cx, |_| {});
22430
22431    // Simple insertion
22432    assert_highlighted_edits(
22433        "Hello, world!",
22434        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22435        true,
22436        cx,
22437        |highlighted_edits, cx| {
22438            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22439            assert_eq!(highlighted_edits.highlights.len(), 1);
22440            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22441            assert_eq!(
22442                highlighted_edits.highlights[0].1.background_color,
22443                Some(cx.theme().status().created_background)
22444            );
22445        },
22446    )
22447    .await;
22448
22449    // Replacement
22450    assert_highlighted_edits(
22451        "This is a test.",
22452        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22453        false,
22454        cx,
22455        |highlighted_edits, cx| {
22456            assert_eq!(highlighted_edits.text, "That is a test.");
22457            assert_eq!(highlighted_edits.highlights.len(), 1);
22458            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22459            assert_eq!(
22460                highlighted_edits.highlights[0].1.background_color,
22461                Some(cx.theme().status().created_background)
22462            );
22463        },
22464    )
22465    .await;
22466
22467    // Multiple edits
22468    assert_highlighted_edits(
22469        "Hello, world!",
22470        vec![
22471            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22472            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22473        ],
22474        false,
22475        cx,
22476        |highlighted_edits, cx| {
22477            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22478            assert_eq!(highlighted_edits.highlights.len(), 2);
22479            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22480            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22481            assert_eq!(
22482                highlighted_edits.highlights[0].1.background_color,
22483                Some(cx.theme().status().created_background)
22484            );
22485            assert_eq!(
22486                highlighted_edits.highlights[1].1.background_color,
22487                Some(cx.theme().status().created_background)
22488            );
22489        },
22490    )
22491    .await;
22492
22493    // Multiple lines with edits
22494    assert_highlighted_edits(
22495        "First line\nSecond line\nThird line\nFourth line",
22496        vec![
22497            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22498            (
22499                Point::new(2, 0)..Point::new(2, 10),
22500                "New third line".to_string(),
22501            ),
22502            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22503        ],
22504        false,
22505        cx,
22506        |highlighted_edits, cx| {
22507            assert_eq!(
22508                highlighted_edits.text,
22509                "Second modified\nNew third line\nFourth updated line"
22510            );
22511            assert_eq!(highlighted_edits.highlights.len(), 3);
22512            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22513            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22514            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22515            for highlight in &highlighted_edits.highlights {
22516                assert_eq!(
22517                    highlight.1.background_color,
22518                    Some(cx.theme().status().created_background)
22519                );
22520            }
22521        },
22522    )
22523    .await;
22524}
22525
22526#[gpui::test]
22527async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22528    init_test(cx, |_| {});
22529
22530    // Deletion
22531    assert_highlighted_edits(
22532        "Hello, world!",
22533        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22534        true,
22535        cx,
22536        |highlighted_edits, cx| {
22537            assert_eq!(highlighted_edits.text, "Hello, world!");
22538            assert_eq!(highlighted_edits.highlights.len(), 1);
22539            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22540            assert_eq!(
22541                highlighted_edits.highlights[0].1.background_color,
22542                Some(cx.theme().status().deleted_background)
22543            );
22544        },
22545    )
22546    .await;
22547
22548    // Insertion
22549    assert_highlighted_edits(
22550        "Hello, world!",
22551        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22552        true,
22553        cx,
22554        |highlighted_edits, cx| {
22555            assert_eq!(highlighted_edits.highlights.len(), 1);
22556            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22557            assert_eq!(
22558                highlighted_edits.highlights[0].1.background_color,
22559                Some(cx.theme().status().created_background)
22560            );
22561        },
22562    )
22563    .await;
22564}
22565
22566async fn assert_highlighted_edits(
22567    text: &str,
22568    edits: Vec<(Range<Point>, String)>,
22569    include_deletions: bool,
22570    cx: &mut TestAppContext,
22571    assertion_fn: impl Fn(HighlightedText, &App),
22572) {
22573    let window = cx.add_window(|window, cx| {
22574        let buffer = MultiBuffer::build_simple(text, cx);
22575        Editor::new(EditorMode::full(), buffer, None, window, cx)
22576    });
22577    let cx = &mut VisualTestContext::from_window(*window, cx);
22578
22579    let (buffer, snapshot) = window
22580        .update(cx, |editor, _window, cx| {
22581            (
22582                editor.buffer().clone(),
22583                editor.buffer().read(cx).snapshot(cx),
22584            )
22585        })
22586        .unwrap();
22587
22588    let edits = edits
22589        .into_iter()
22590        .map(|(range, edit)| {
22591            (
22592                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22593                edit,
22594            )
22595        })
22596        .collect::<Vec<_>>();
22597
22598    let text_anchor_edits = edits
22599        .clone()
22600        .into_iter()
22601        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22602        .collect::<Vec<_>>();
22603
22604    let edit_preview = window
22605        .update(cx, |_, _window, cx| {
22606            buffer
22607                .read(cx)
22608                .as_singleton()
22609                .unwrap()
22610                .read(cx)
22611                .preview_edits(text_anchor_edits.into(), cx)
22612        })
22613        .unwrap()
22614        .await;
22615
22616    cx.update(|_window, cx| {
22617        let highlighted_edits = edit_prediction_edit_text(
22618            snapshot.as_singleton().unwrap().2,
22619            &edits,
22620            &edit_preview,
22621            include_deletions,
22622            cx,
22623        );
22624        assertion_fn(highlighted_edits, cx)
22625    });
22626}
22627
22628#[track_caller]
22629fn assert_breakpoint(
22630    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22631    path: &Arc<Path>,
22632    expected: Vec<(u32, Breakpoint)>,
22633) {
22634    if expected.is_empty() {
22635        assert!(!breakpoints.contains_key(path), "{}", path.display());
22636    } else {
22637        let mut breakpoint = breakpoints
22638            .get(path)
22639            .unwrap()
22640            .iter()
22641            .map(|breakpoint| {
22642                (
22643                    breakpoint.row,
22644                    Breakpoint {
22645                        message: breakpoint.message.clone(),
22646                        state: breakpoint.state,
22647                        condition: breakpoint.condition.clone(),
22648                        hit_condition: breakpoint.hit_condition.clone(),
22649                    },
22650                )
22651            })
22652            .collect::<Vec<_>>();
22653
22654        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22655
22656        assert_eq!(expected, breakpoint);
22657    }
22658}
22659
22660fn add_log_breakpoint_at_cursor(
22661    editor: &mut Editor,
22662    log_message: &str,
22663    window: &mut Window,
22664    cx: &mut Context<Editor>,
22665) {
22666    let (anchor, bp) = editor
22667        .breakpoints_at_cursors(window, cx)
22668        .first()
22669        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22670        .unwrap_or_else(|| {
22671            let cursor_position: Point = editor.selections.newest(cx).head();
22672
22673            let breakpoint_position = editor
22674                .snapshot(window, cx)
22675                .display_snapshot
22676                .buffer_snapshot()
22677                .anchor_before(Point::new(cursor_position.row, 0));
22678
22679            (breakpoint_position, Breakpoint::new_log(log_message))
22680        });
22681
22682    editor.edit_breakpoint_at_anchor(
22683        anchor,
22684        bp,
22685        BreakpointEditAction::EditLogMessage(log_message.into()),
22686        cx,
22687    );
22688}
22689
22690#[gpui::test]
22691async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22692    init_test(cx, |_| {});
22693
22694    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22695    let fs = FakeFs::new(cx.executor());
22696    fs.insert_tree(
22697        path!("/a"),
22698        json!({
22699            "main.rs": sample_text,
22700        }),
22701    )
22702    .await;
22703    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22704    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22705    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22706
22707    let fs = FakeFs::new(cx.executor());
22708    fs.insert_tree(
22709        path!("/a"),
22710        json!({
22711            "main.rs": sample_text,
22712        }),
22713    )
22714    .await;
22715    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22716    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22717    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22718    let worktree_id = workspace
22719        .update(cx, |workspace, _window, cx| {
22720            workspace.project().update(cx, |project, cx| {
22721                project.worktrees(cx).next().unwrap().read(cx).id()
22722            })
22723        })
22724        .unwrap();
22725
22726    let buffer = project
22727        .update(cx, |project, cx| {
22728            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22729        })
22730        .await
22731        .unwrap();
22732
22733    let (editor, cx) = cx.add_window_view(|window, cx| {
22734        Editor::new(
22735            EditorMode::full(),
22736            MultiBuffer::build_from_buffer(buffer, cx),
22737            Some(project.clone()),
22738            window,
22739            cx,
22740        )
22741    });
22742
22743    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22744    let abs_path = project.read_with(cx, |project, cx| {
22745        project
22746            .absolute_path(&project_path, cx)
22747            .map(Arc::from)
22748            .unwrap()
22749    });
22750
22751    // assert we can add breakpoint on the first line
22752    editor.update_in(cx, |editor, window, cx| {
22753        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22754        editor.move_to_end(&MoveToEnd, window, cx);
22755        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22756    });
22757
22758    let breakpoints = editor.update(cx, |editor, cx| {
22759        editor
22760            .breakpoint_store()
22761            .as_ref()
22762            .unwrap()
22763            .read(cx)
22764            .all_source_breakpoints(cx)
22765    });
22766
22767    assert_eq!(1, breakpoints.len());
22768    assert_breakpoint(
22769        &breakpoints,
22770        &abs_path,
22771        vec![
22772            (0, Breakpoint::new_standard()),
22773            (3, Breakpoint::new_standard()),
22774        ],
22775    );
22776
22777    editor.update_in(cx, |editor, window, cx| {
22778        editor.move_to_beginning(&MoveToBeginning, window, cx);
22779        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22780    });
22781
22782    let breakpoints = editor.update(cx, |editor, cx| {
22783        editor
22784            .breakpoint_store()
22785            .as_ref()
22786            .unwrap()
22787            .read(cx)
22788            .all_source_breakpoints(cx)
22789    });
22790
22791    assert_eq!(1, breakpoints.len());
22792    assert_breakpoint(
22793        &breakpoints,
22794        &abs_path,
22795        vec![(3, Breakpoint::new_standard())],
22796    );
22797
22798    editor.update_in(cx, |editor, window, cx| {
22799        editor.move_to_end(&MoveToEnd, window, cx);
22800        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22801    });
22802
22803    let breakpoints = editor.update(cx, |editor, cx| {
22804        editor
22805            .breakpoint_store()
22806            .as_ref()
22807            .unwrap()
22808            .read(cx)
22809            .all_source_breakpoints(cx)
22810    });
22811
22812    assert_eq!(0, breakpoints.len());
22813    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22814}
22815
22816#[gpui::test]
22817async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22818    init_test(cx, |_| {});
22819
22820    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22821
22822    let fs = FakeFs::new(cx.executor());
22823    fs.insert_tree(
22824        path!("/a"),
22825        json!({
22826            "main.rs": sample_text,
22827        }),
22828    )
22829    .await;
22830    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22831    let (workspace, cx) =
22832        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22833
22834    let worktree_id = workspace.update(cx, |workspace, cx| {
22835        workspace.project().update(cx, |project, cx| {
22836            project.worktrees(cx).next().unwrap().read(cx).id()
22837        })
22838    });
22839
22840    let buffer = project
22841        .update(cx, |project, cx| {
22842            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22843        })
22844        .await
22845        .unwrap();
22846
22847    let (editor, cx) = cx.add_window_view(|window, cx| {
22848        Editor::new(
22849            EditorMode::full(),
22850            MultiBuffer::build_from_buffer(buffer, cx),
22851            Some(project.clone()),
22852            window,
22853            cx,
22854        )
22855    });
22856
22857    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22858    let abs_path = project.read_with(cx, |project, cx| {
22859        project
22860            .absolute_path(&project_path, cx)
22861            .map(Arc::from)
22862            .unwrap()
22863    });
22864
22865    editor.update_in(cx, |editor, window, cx| {
22866        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22867    });
22868
22869    let breakpoints = editor.update(cx, |editor, cx| {
22870        editor
22871            .breakpoint_store()
22872            .as_ref()
22873            .unwrap()
22874            .read(cx)
22875            .all_source_breakpoints(cx)
22876    });
22877
22878    assert_breakpoint(
22879        &breakpoints,
22880        &abs_path,
22881        vec![(0, Breakpoint::new_log("hello world"))],
22882    );
22883
22884    // Removing a log message from a log breakpoint should remove it
22885    editor.update_in(cx, |editor, window, cx| {
22886        add_log_breakpoint_at_cursor(editor, "", window, cx);
22887    });
22888
22889    let breakpoints = editor.update(cx, |editor, cx| {
22890        editor
22891            .breakpoint_store()
22892            .as_ref()
22893            .unwrap()
22894            .read(cx)
22895            .all_source_breakpoints(cx)
22896    });
22897
22898    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22899
22900    editor.update_in(cx, |editor, window, cx| {
22901        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22902        editor.move_to_end(&MoveToEnd, window, cx);
22903        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22904        // Not adding a log message to a standard breakpoint shouldn't remove it
22905        add_log_breakpoint_at_cursor(editor, "", window, cx);
22906    });
22907
22908    let breakpoints = editor.update(cx, |editor, cx| {
22909        editor
22910            .breakpoint_store()
22911            .as_ref()
22912            .unwrap()
22913            .read(cx)
22914            .all_source_breakpoints(cx)
22915    });
22916
22917    assert_breakpoint(
22918        &breakpoints,
22919        &abs_path,
22920        vec![
22921            (0, Breakpoint::new_standard()),
22922            (3, Breakpoint::new_standard()),
22923        ],
22924    );
22925
22926    editor.update_in(cx, |editor, window, cx| {
22927        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22928    });
22929
22930    let breakpoints = editor.update(cx, |editor, cx| {
22931        editor
22932            .breakpoint_store()
22933            .as_ref()
22934            .unwrap()
22935            .read(cx)
22936            .all_source_breakpoints(cx)
22937    });
22938
22939    assert_breakpoint(
22940        &breakpoints,
22941        &abs_path,
22942        vec![
22943            (0, Breakpoint::new_standard()),
22944            (3, Breakpoint::new_log("hello world")),
22945        ],
22946    );
22947
22948    editor.update_in(cx, |editor, window, cx| {
22949        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22950    });
22951
22952    let breakpoints = editor.update(cx, |editor, cx| {
22953        editor
22954            .breakpoint_store()
22955            .as_ref()
22956            .unwrap()
22957            .read(cx)
22958            .all_source_breakpoints(cx)
22959    });
22960
22961    assert_breakpoint(
22962        &breakpoints,
22963        &abs_path,
22964        vec![
22965            (0, Breakpoint::new_standard()),
22966            (3, Breakpoint::new_log("hello Earth!!")),
22967        ],
22968    );
22969}
22970
22971/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22972/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22973/// or when breakpoints were placed out of order. This tests for a regression too
22974#[gpui::test]
22975async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22976    init_test(cx, |_| {});
22977
22978    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22979    let fs = FakeFs::new(cx.executor());
22980    fs.insert_tree(
22981        path!("/a"),
22982        json!({
22983            "main.rs": sample_text,
22984        }),
22985    )
22986    .await;
22987    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22988    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22989    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22990
22991    let fs = FakeFs::new(cx.executor());
22992    fs.insert_tree(
22993        path!("/a"),
22994        json!({
22995            "main.rs": sample_text,
22996        }),
22997    )
22998    .await;
22999    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23000    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23001    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23002    let worktree_id = workspace
23003        .update(cx, |workspace, _window, cx| {
23004            workspace.project().update(cx, |project, cx| {
23005                project.worktrees(cx).next().unwrap().read(cx).id()
23006            })
23007        })
23008        .unwrap();
23009
23010    let buffer = project
23011        .update(cx, |project, cx| {
23012            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23013        })
23014        .await
23015        .unwrap();
23016
23017    let (editor, cx) = cx.add_window_view(|window, cx| {
23018        Editor::new(
23019            EditorMode::full(),
23020            MultiBuffer::build_from_buffer(buffer, cx),
23021            Some(project.clone()),
23022            window,
23023            cx,
23024        )
23025    });
23026
23027    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23028    let abs_path = project.read_with(cx, |project, cx| {
23029        project
23030            .absolute_path(&project_path, cx)
23031            .map(Arc::from)
23032            .unwrap()
23033    });
23034
23035    // assert we can add breakpoint on the first line
23036    editor.update_in(cx, |editor, window, cx| {
23037        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23038        editor.move_to_end(&MoveToEnd, window, cx);
23039        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23040        editor.move_up(&MoveUp, window, cx);
23041        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23042    });
23043
23044    let breakpoints = editor.update(cx, |editor, cx| {
23045        editor
23046            .breakpoint_store()
23047            .as_ref()
23048            .unwrap()
23049            .read(cx)
23050            .all_source_breakpoints(cx)
23051    });
23052
23053    assert_eq!(1, breakpoints.len());
23054    assert_breakpoint(
23055        &breakpoints,
23056        &abs_path,
23057        vec![
23058            (0, Breakpoint::new_standard()),
23059            (2, Breakpoint::new_standard()),
23060            (3, Breakpoint::new_standard()),
23061        ],
23062    );
23063
23064    editor.update_in(cx, |editor, window, cx| {
23065        editor.move_to_beginning(&MoveToBeginning, window, cx);
23066        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23067        editor.move_to_end(&MoveToEnd, window, cx);
23068        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23069        // Disabling a breakpoint that doesn't exist should do nothing
23070        editor.move_up(&MoveUp, window, cx);
23071        editor.move_up(&MoveUp, window, cx);
23072        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23073    });
23074
23075    let breakpoints = editor.update(cx, |editor, cx| {
23076        editor
23077            .breakpoint_store()
23078            .as_ref()
23079            .unwrap()
23080            .read(cx)
23081            .all_source_breakpoints(cx)
23082    });
23083
23084    let disable_breakpoint = {
23085        let mut bp = Breakpoint::new_standard();
23086        bp.state = BreakpointState::Disabled;
23087        bp
23088    };
23089
23090    assert_eq!(1, breakpoints.len());
23091    assert_breakpoint(
23092        &breakpoints,
23093        &abs_path,
23094        vec![
23095            (0, disable_breakpoint.clone()),
23096            (2, Breakpoint::new_standard()),
23097            (3, disable_breakpoint.clone()),
23098        ],
23099    );
23100
23101    editor.update_in(cx, |editor, window, cx| {
23102        editor.move_to_beginning(&MoveToBeginning, window, cx);
23103        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23104        editor.move_to_end(&MoveToEnd, window, cx);
23105        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23106        editor.move_up(&MoveUp, window, cx);
23107        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23108    });
23109
23110    let breakpoints = editor.update(cx, |editor, cx| {
23111        editor
23112            .breakpoint_store()
23113            .as_ref()
23114            .unwrap()
23115            .read(cx)
23116            .all_source_breakpoints(cx)
23117    });
23118
23119    assert_eq!(1, breakpoints.len());
23120    assert_breakpoint(
23121        &breakpoints,
23122        &abs_path,
23123        vec![
23124            (0, Breakpoint::new_standard()),
23125            (2, disable_breakpoint),
23126            (3, Breakpoint::new_standard()),
23127        ],
23128    );
23129}
23130
23131#[gpui::test]
23132async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23133    init_test(cx, |_| {});
23134    let capabilities = lsp::ServerCapabilities {
23135        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23136            prepare_provider: Some(true),
23137            work_done_progress_options: Default::default(),
23138        })),
23139        ..Default::default()
23140    };
23141    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23142
23143    cx.set_state(indoc! {"
23144        struct Fˇoo {}
23145    "});
23146
23147    cx.update_editor(|editor, _, cx| {
23148        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23149        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23150        editor.highlight_background::<DocumentHighlightRead>(
23151            &[highlight_range],
23152            |theme| theme.colors().editor_document_highlight_read_background,
23153            cx,
23154        );
23155    });
23156
23157    let mut prepare_rename_handler = cx
23158        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23159            move |_, _, _| async move {
23160                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23161                    start: lsp::Position {
23162                        line: 0,
23163                        character: 7,
23164                    },
23165                    end: lsp::Position {
23166                        line: 0,
23167                        character: 10,
23168                    },
23169                })))
23170            },
23171        );
23172    let prepare_rename_task = cx
23173        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23174        .expect("Prepare rename was not started");
23175    prepare_rename_handler.next().await.unwrap();
23176    prepare_rename_task.await.expect("Prepare rename failed");
23177
23178    let mut rename_handler =
23179        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23180            let edit = lsp::TextEdit {
23181                range: lsp::Range {
23182                    start: lsp::Position {
23183                        line: 0,
23184                        character: 7,
23185                    },
23186                    end: lsp::Position {
23187                        line: 0,
23188                        character: 10,
23189                    },
23190                },
23191                new_text: "FooRenamed".to_string(),
23192            };
23193            Ok(Some(lsp::WorkspaceEdit::new(
23194                // Specify the same edit twice
23195                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23196            )))
23197        });
23198    let rename_task = cx
23199        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23200        .expect("Confirm rename was not started");
23201    rename_handler.next().await.unwrap();
23202    rename_task.await.expect("Confirm rename failed");
23203    cx.run_until_parked();
23204
23205    // Despite two edits, only one is actually applied as those are identical
23206    cx.assert_editor_state(indoc! {"
23207        struct FooRenamedˇ {}
23208    "});
23209}
23210
23211#[gpui::test]
23212async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23213    init_test(cx, |_| {});
23214    // These capabilities indicate that the server does not support prepare rename.
23215    let capabilities = lsp::ServerCapabilities {
23216        rename_provider: Some(lsp::OneOf::Left(true)),
23217        ..Default::default()
23218    };
23219    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23220
23221    cx.set_state(indoc! {"
23222        struct Fˇoo {}
23223    "});
23224
23225    cx.update_editor(|editor, _window, cx| {
23226        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23227        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23228        editor.highlight_background::<DocumentHighlightRead>(
23229            &[highlight_range],
23230            |theme| theme.colors().editor_document_highlight_read_background,
23231            cx,
23232        );
23233    });
23234
23235    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23236        .expect("Prepare rename was not started")
23237        .await
23238        .expect("Prepare rename failed");
23239
23240    let mut rename_handler =
23241        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23242            let edit = lsp::TextEdit {
23243                range: lsp::Range {
23244                    start: lsp::Position {
23245                        line: 0,
23246                        character: 7,
23247                    },
23248                    end: lsp::Position {
23249                        line: 0,
23250                        character: 10,
23251                    },
23252                },
23253                new_text: "FooRenamed".to_string(),
23254            };
23255            Ok(Some(lsp::WorkspaceEdit::new(
23256                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23257            )))
23258        });
23259    let rename_task = cx
23260        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23261        .expect("Confirm rename was not started");
23262    rename_handler.next().await.unwrap();
23263    rename_task.await.expect("Confirm rename failed");
23264    cx.run_until_parked();
23265
23266    // Correct range is renamed, as `surrounding_word` is used to find it.
23267    cx.assert_editor_state(indoc! {"
23268        struct FooRenamedˇ {}
23269    "});
23270}
23271
23272#[gpui::test]
23273async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23274    init_test(cx, |_| {});
23275    let mut cx = EditorTestContext::new(cx).await;
23276
23277    let language = Arc::new(
23278        Language::new(
23279            LanguageConfig::default(),
23280            Some(tree_sitter_html::LANGUAGE.into()),
23281        )
23282        .with_brackets_query(
23283            r#"
23284            ("<" @open "/>" @close)
23285            ("</" @open ">" @close)
23286            ("<" @open ">" @close)
23287            ("\"" @open "\"" @close)
23288            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23289        "#,
23290        )
23291        .unwrap(),
23292    );
23293    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23294
23295    cx.set_state(indoc! {"
23296        <span>ˇ</span>
23297    "});
23298    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23299    cx.assert_editor_state(indoc! {"
23300        <span>
23301        ˇ
23302        </span>
23303    "});
23304
23305    cx.set_state(indoc! {"
23306        <span><span></span>ˇ</span>
23307    "});
23308    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23309    cx.assert_editor_state(indoc! {"
23310        <span><span></span>
23311        ˇ</span>
23312    "});
23313
23314    cx.set_state(indoc! {"
23315        <span>ˇ
23316        </span>
23317    "});
23318    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23319    cx.assert_editor_state(indoc! {"
23320        <span>
23321        ˇ
23322        </span>
23323    "});
23324}
23325
23326#[gpui::test(iterations = 10)]
23327async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23328    init_test(cx, |_| {});
23329
23330    let fs = FakeFs::new(cx.executor());
23331    fs.insert_tree(
23332        path!("/dir"),
23333        json!({
23334            "a.ts": "a",
23335        }),
23336    )
23337    .await;
23338
23339    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23340    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23341    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23342
23343    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23344    language_registry.add(Arc::new(Language::new(
23345        LanguageConfig {
23346            name: "TypeScript".into(),
23347            matcher: LanguageMatcher {
23348                path_suffixes: vec!["ts".to_string()],
23349                ..Default::default()
23350            },
23351            ..Default::default()
23352        },
23353        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23354    )));
23355    let mut fake_language_servers = language_registry.register_fake_lsp(
23356        "TypeScript",
23357        FakeLspAdapter {
23358            capabilities: lsp::ServerCapabilities {
23359                code_lens_provider: Some(lsp::CodeLensOptions {
23360                    resolve_provider: Some(true),
23361                }),
23362                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23363                    commands: vec!["_the/command".to_string()],
23364                    ..lsp::ExecuteCommandOptions::default()
23365                }),
23366                ..lsp::ServerCapabilities::default()
23367            },
23368            ..FakeLspAdapter::default()
23369        },
23370    );
23371
23372    let editor = workspace
23373        .update(cx, |workspace, window, cx| {
23374            workspace.open_abs_path(
23375                PathBuf::from(path!("/dir/a.ts")),
23376                OpenOptions::default(),
23377                window,
23378                cx,
23379            )
23380        })
23381        .unwrap()
23382        .await
23383        .unwrap()
23384        .downcast::<Editor>()
23385        .unwrap();
23386    cx.executor().run_until_parked();
23387
23388    let fake_server = fake_language_servers.next().await.unwrap();
23389
23390    let buffer = editor.update(cx, |editor, cx| {
23391        editor
23392            .buffer()
23393            .read(cx)
23394            .as_singleton()
23395            .expect("have opened a single file by path")
23396    });
23397
23398    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23399    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23400    drop(buffer_snapshot);
23401    let actions = cx
23402        .update_window(*workspace, |_, window, cx| {
23403            project.code_actions(&buffer, anchor..anchor, window, cx)
23404        })
23405        .unwrap();
23406
23407    fake_server
23408        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23409            Ok(Some(vec![
23410                lsp::CodeLens {
23411                    range: lsp::Range::default(),
23412                    command: Some(lsp::Command {
23413                        title: "Code lens command".to_owned(),
23414                        command: "_the/command".to_owned(),
23415                        arguments: None,
23416                    }),
23417                    data: None,
23418                },
23419                lsp::CodeLens {
23420                    range: lsp::Range::default(),
23421                    command: Some(lsp::Command {
23422                        title: "Command not in capabilities".to_owned(),
23423                        command: "not in capabilities".to_owned(),
23424                        arguments: None,
23425                    }),
23426                    data: None,
23427                },
23428                lsp::CodeLens {
23429                    range: lsp::Range {
23430                        start: lsp::Position {
23431                            line: 1,
23432                            character: 1,
23433                        },
23434                        end: lsp::Position {
23435                            line: 1,
23436                            character: 1,
23437                        },
23438                    },
23439                    command: Some(lsp::Command {
23440                        title: "Command not in range".to_owned(),
23441                        command: "_the/command".to_owned(),
23442                        arguments: None,
23443                    }),
23444                    data: None,
23445                },
23446            ]))
23447        })
23448        .next()
23449        .await;
23450
23451    let actions = actions.await.unwrap();
23452    assert_eq!(
23453        actions.len(),
23454        1,
23455        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23456    );
23457    let action = actions[0].clone();
23458    let apply = project.update(cx, |project, cx| {
23459        project.apply_code_action(buffer.clone(), action, true, cx)
23460    });
23461
23462    // Resolving the code action does not populate its edits. In absence of
23463    // edits, we must execute the given command.
23464    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23465        |mut lens, _| async move {
23466            let lens_command = lens.command.as_mut().expect("should have a command");
23467            assert_eq!(lens_command.title, "Code lens command");
23468            lens_command.arguments = Some(vec![json!("the-argument")]);
23469            Ok(lens)
23470        },
23471    );
23472
23473    // While executing the command, the language server sends the editor
23474    // a `workspaceEdit` request.
23475    fake_server
23476        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23477            let fake = fake_server.clone();
23478            move |params, _| {
23479                assert_eq!(params.command, "_the/command");
23480                let fake = fake.clone();
23481                async move {
23482                    fake.server
23483                        .request::<lsp::request::ApplyWorkspaceEdit>(
23484                            lsp::ApplyWorkspaceEditParams {
23485                                label: None,
23486                                edit: lsp::WorkspaceEdit {
23487                                    changes: Some(
23488                                        [(
23489                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23490                                            vec![lsp::TextEdit {
23491                                                range: lsp::Range::new(
23492                                                    lsp::Position::new(0, 0),
23493                                                    lsp::Position::new(0, 0),
23494                                                ),
23495                                                new_text: "X".into(),
23496                                            }],
23497                                        )]
23498                                        .into_iter()
23499                                        .collect(),
23500                                    ),
23501                                    ..lsp::WorkspaceEdit::default()
23502                                },
23503                            },
23504                        )
23505                        .await
23506                        .into_response()
23507                        .unwrap();
23508                    Ok(Some(json!(null)))
23509                }
23510            }
23511        })
23512        .next()
23513        .await;
23514
23515    // Applying the code lens command returns a project transaction containing the edits
23516    // sent by the language server in its `workspaceEdit` request.
23517    let transaction = apply.await.unwrap();
23518    assert!(transaction.0.contains_key(&buffer));
23519    buffer.update(cx, |buffer, cx| {
23520        assert_eq!(buffer.text(), "Xa");
23521        buffer.undo(cx);
23522        assert_eq!(buffer.text(), "a");
23523    });
23524
23525    let actions_after_edits = cx
23526        .update_window(*workspace, |_, window, cx| {
23527            project.code_actions(&buffer, anchor..anchor, window, cx)
23528        })
23529        .unwrap()
23530        .await
23531        .unwrap();
23532    assert_eq!(
23533        actions, actions_after_edits,
23534        "For the same selection, same code lens actions should be returned"
23535    );
23536
23537    let _responses =
23538        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23539            panic!("No more code lens requests are expected");
23540        });
23541    editor.update_in(cx, |editor, window, cx| {
23542        editor.select_all(&SelectAll, window, cx);
23543    });
23544    cx.executor().run_until_parked();
23545    let new_actions = cx
23546        .update_window(*workspace, |_, window, cx| {
23547            project.code_actions(&buffer, anchor..anchor, window, cx)
23548        })
23549        .unwrap()
23550        .await
23551        .unwrap();
23552    assert_eq!(
23553        actions, new_actions,
23554        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23555    );
23556}
23557
23558#[gpui::test]
23559async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23560    init_test(cx, |_| {});
23561
23562    let fs = FakeFs::new(cx.executor());
23563    let main_text = r#"fn main() {
23564println!("1");
23565println!("2");
23566println!("3");
23567println!("4");
23568println!("5");
23569}"#;
23570    let lib_text = "mod foo {}";
23571    fs.insert_tree(
23572        path!("/a"),
23573        json!({
23574            "lib.rs": lib_text,
23575            "main.rs": main_text,
23576        }),
23577    )
23578    .await;
23579
23580    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23581    let (workspace, cx) =
23582        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23583    let worktree_id = workspace.update(cx, |workspace, cx| {
23584        workspace.project().update(cx, |project, cx| {
23585            project.worktrees(cx).next().unwrap().read(cx).id()
23586        })
23587    });
23588
23589    let expected_ranges = vec![
23590        Point::new(0, 0)..Point::new(0, 0),
23591        Point::new(1, 0)..Point::new(1, 1),
23592        Point::new(2, 0)..Point::new(2, 2),
23593        Point::new(3, 0)..Point::new(3, 3),
23594    ];
23595
23596    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23597    let editor_1 = workspace
23598        .update_in(cx, |workspace, window, cx| {
23599            workspace.open_path(
23600                (worktree_id, rel_path("main.rs")),
23601                Some(pane_1.downgrade()),
23602                true,
23603                window,
23604                cx,
23605            )
23606        })
23607        .unwrap()
23608        .await
23609        .downcast::<Editor>()
23610        .unwrap();
23611    pane_1.update(cx, |pane, cx| {
23612        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23613        open_editor.update(cx, |editor, cx| {
23614            assert_eq!(
23615                editor.display_text(cx),
23616                main_text,
23617                "Original main.rs text on initial open",
23618            );
23619            assert_eq!(
23620                editor
23621                    .selections
23622                    .all::<Point>(cx)
23623                    .into_iter()
23624                    .map(|s| s.range())
23625                    .collect::<Vec<_>>(),
23626                vec![Point::zero()..Point::zero()],
23627                "Default selections on initial open",
23628            );
23629        })
23630    });
23631    editor_1.update_in(cx, |editor, window, cx| {
23632        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23633            s.select_ranges(expected_ranges.clone());
23634        });
23635    });
23636
23637    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23638        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23639    });
23640    let editor_2 = workspace
23641        .update_in(cx, |workspace, window, cx| {
23642            workspace.open_path(
23643                (worktree_id, rel_path("main.rs")),
23644                Some(pane_2.downgrade()),
23645                true,
23646                window,
23647                cx,
23648            )
23649        })
23650        .unwrap()
23651        .await
23652        .downcast::<Editor>()
23653        .unwrap();
23654    pane_2.update(cx, |pane, cx| {
23655        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23656        open_editor.update(cx, |editor, cx| {
23657            assert_eq!(
23658                editor.display_text(cx),
23659                main_text,
23660                "Original main.rs text on initial open in another panel",
23661            );
23662            assert_eq!(
23663                editor
23664                    .selections
23665                    .all::<Point>(cx)
23666                    .into_iter()
23667                    .map(|s| s.range())
23668                    .collect::<Vec<_>>(),
23669                vec![Point::zero()..Point::zero()],
23670                "Default selections on initial open in another panel",
23671            );
23672        })
23673    });
23674
23675    editor_2.update_in(cx, |editor, window, cx| {
23676        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23677    });
23678
23679    let _other_editor_1 = workspace
23680        .update_in(cx, |workspace, window, cx| {
23681            workspace.open_path(
23682                (worktree_id, rel_path("lib.rs")),
23683                Some(pane_1.downgrade()),
23684                true,
23685                window,
23686                cx,
23687            )
23688        })
23689        .unwrap()
23690        .await
23691        .downcast::<Editor>()
23692        .unwrap();
23693    pane_1
23694        .update_in(cx, |pane, window, cx| {
23695            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23696        })
23697        .await
23698        .unwrap();
23699    drop(editor_1);
23700    pane_1.update(cx, |pane, cx| {
23701        pane.active_item()
23702            .unwrap()
23703            .downcast::<Editor>()
23704            .unwrap()
23705            .update(cx, |editor, cx| {
23706                assert_eq!(
23707                    editor.display_text(cx),
23708                    lib_text,
23709                    "Other file should be open and active",
23710                );
23711            });
23712        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23713    });
23714
23715    let _other_editor_2 = workspace
23716        .update_in(cx, |workspace, window, cx| {
23717            workspace.open_path(
23718                (worktree_id, rel_path("lib.rs")),
23719                Some(pane_2.downgrade()),
23720                true,
23721                window,
23722                cx,
23723            )
23724        })
23725        .unwrap()
23726        .await
23727        .downcast::<Editor>()
23728        .unwrap();
23729    pane_2
23730        .update_in(cx, |pane, window, cx| {
23731            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23732        })
23733        .await
23734        .unwrap();
23735    drop(editor_2);
23736    pane_2.update(cx, |pane, cx| {
23737        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23738        open_editor.update(cx, |editor, cx| {
23739            assert_eq!(
23740                editor.display_text(cx),
23741                lib_text,
23742                "Other file should be open and active in another panel too",
23743            );
23744        });
23745        assert_eq!(
23746            pane.items().count(),
23747            1,
23748            "No other editors should be open in another pane",
23749        );
23750    });
23751
23752    let _editor_1_reopened = workspace
23753        .update_in(cx, |workspace, window, cx| {
23754            workspace.open_path(
23755                (worktree_id, rel_path("main.rs")),
23756                Some(pane_1.downgrade()),
23757                true,
23758                window,
23759                cx,
23760            )
23761        })
23762        .unwrap()
23763        .await
23764        .downcast::<Editor>()
23765        .unwrap();
23766    let _editor_2_reopened = workspace
23767        .update_in(cx, |workspace, window, cx| {
23768            workspace.open_path(
23769                (worktree_id, rel_path("main.rs")),
23770                Some(pane_2.downgrade()),
23771                true,
23772                window,
23773                cx,
23774            )
23775        })
23776        .unwrap()
23777        .await
23778        .downcast::<Editor>()
23779        .unwrap();
23780    pane_1.update(cx, |pane, cx| {
23781        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23782        open_editor.update(cx, |editor, cx| {
23783            assert_eq!(
23784                editor.display_text(cx),
23785                main_text,
23786                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23787            );
23788            assert_eq!(
23789                editor
23790                    .selections
23791                    .all::<Point>(cx)
23792                    .into_iter()
23793                    .map(|s| s.range())
23794                    .collect::<Vec<_>>(),
23795                expected_ranges,
23796                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23797            );
23798        })
23799    });
23800    pane_2.update(cx, |pane, cx| {
23801        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23802        open_editor.update(cx, |editor, cx| {
23803            assert_eq!(
23804                editor.display_text(cx),
23805                r#"fn main() {
23806⋯rintln!("1");
23807⋯intln!("2");
23808⋯ntln!("3");
23809println!("4");
23810println!("5");
23811}"#,
23812                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23813            );
23814            assert_eq!(
23815                editor
23816                    .selections
23817                    .all::<Point>(cx)
23818                    .into_iter()
23819                    .map(|s| s.range())
23820                    .collect::<Vec<_>>(),
23821                vec![Point::zero()..Point::zero()],
23822                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23823            );
23824        })
23825    });
23826}
23827
23828#[gpui::test]
23829async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23830    init_test(cx, |_| {});
23831
23832    let fs = FakeFs::new(cx.executor());
23833    let main_text = r#"fn main() {
23834println!("1");
23835println!("2");
23836println!("3");
23837println!("4");
23838println!("5");
23839}"#;
23840    let lib_text = "mod foo {}";
23841    fs.insert_tree(
23842        path!("/a"),
23843        json!({
23844            "lib.rs": lib_text,
23845            "main.rs": main_text,
23846        }),
23847    )
23848    .await;
23849
23850    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23851    let (workspace, cx) =
23852        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23853    let worktree_id = workspace.update(cx, |workspace, cx| {
23854        workspace.project().update(cx, |project, cx| {
23855            project.worktrees(cx).next().unwrap().read(cx).id()
23856        })
23857    });
23858
23859    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23860    let editor = workspace
23861        .update_in(cx, |workspace, window, cx| {
23862            workspace.open_path(
23863                (worktree_id, rel_path("main.rs")),
23864                Some(pane.downgrade()),
23865                true,
23866                window,
23867                cx,
23868            )
23869        })
23870        .unwrap()
23871        .await
23872        .downcast::<Editor>()
23873        .unwrap();
23874    pane.update(cx, |pane, cx| {
23875        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23876        open_editor.update(cx, |editor, cx| {
23877            assert_eq!(
23878                editor.display_text(cx),
23879                main_text,
23880                "Original main.rs text on initial open",
23881            );
23882        })
23883    });
23884    editor.update_in(cx, |editor, window, cx| {
23885        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23886    });
23887
23888    cx.update_global(|store: &mut SettingsStore, cx| {
23889        store.update_user_settings(cx, |s| {
23890            s.workspace.restore_on_file_reopen = Some(false);
23891        });
23892    });
23893    editor.update_in(cx, |editor, window, cx| {
23894        editor.fold_ranges(
23895            vec![
23896                Point::new(1, 0)..Point::new(1, 1),
23897                Point::new(2, 0)..Point::new(2, 2),
23898                Point::new(3, 0)..Point::new(3, 3),
23899            ],
23900            false,
23901            window,
23902            cx,
23903        );
23904    });
23905    pane.update_in(cx, |pane, window, cx| {
23906        pane.close_all_items(&CloseAllItems::default(), window, cx)
23907    })
23908    .await
23909    .unwrap();
23910    pane.update(cx, |pane, _| {
23911        assert!(pane.active_item().is_none());
23912    });
23913    cx.update_global(|store: &mut SettingsStore, cx| {
23914        store.update_user_settings(cx, |s| {
23915            s.workspace.restore_on_file_reopen = Some(true);
23916        });
23917    });
23918
23919    let _editor_reopened = workspace
23920        .update_in(cx, |workspace, window, cx| {
23921            workspace.open_path(
23922                (worktree_id, rel_path("main.rs")),
23923                Some(pane.downgrade()),
23924                true,
23925                window,
23926                cx,
23927            )
23928        })
23929        .unwrap()
23930        .await
23931        .downcast::<Editor>()
23932        .unwrap();
23933    pane.update(cx, |pane, cx| {
23934        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23935        open_editor.update(cx, |editor, cx| {
23936            assert_eq!(
23937                editor.display_text(cx),
23938                main_text,
23939                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23940            );
23941        })
23942    });
23943}
23944
23945#[gpui::test]
23946async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23947    struct EmptyModalView {
23948        focus_handle: gpui::FocusHandle,
23949    }
23950    impl EventEmitter<DismissEvent> for EmptyModalView {}
23951    impl Render for EmptyModalView {
23952        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23953            div()
23954        }
23955    }
23956    impl Focusable for EmptyModalView {
23957        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23958            self.focus_handle.clone()
23959        }
23960    }
23961    impl workspace::ModalView for EmptyModalView {}
23962    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23963        EmptyModalView {
23964            focus_handle: cx.focus_handle(),
23965        }
23966    }
23967
23968    init_test(cx, |_| {});
23969
23970    let fs = FakeFs::new(cx.executor());
23971    let project = Project::test(fs, [], cx).await;
23972    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23973    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23974    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23975    let editor = cx.new_window_entity(|window, cx| {
23976        Editor::new(
23977            EditorMode::full(),
23978            buffer,
23979            Some(project.clone()),
23980            window,
23981            cx,
23982        )
23983    });
23984    workspace
23985        .update(cx, |workspace, window, cx| {
23986            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23987        })
23988        .unwrap();
23989    editor.update_in(cx, |editor, window, cx| {
23990        editor.open_context_menu(&OpenContextMenu, window, cx);
23991        assert!(editor.mouse_context_menu.is_some());
23992    });
23993    workspace
23994        .update(cx, |workspace, window, cx| {
23995            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23996        })
23997        .unwrap();
23998    cx.read(|cx| {
23999        assert!(editor.read(cx).mouse_context_menu.is_none());
24000    });
24001}
24002
24003fn set_linked_edit_ranges(
24004    opening: (Point, Point),
24005    closing: (Point, Point),
24006    editor: &mut Editor,
24007    cx: &mut Context<Editor>,
24008) {
24009    let Some((buffer, _)) = editor
24010        .buffer
24011        .read(cx)
24012        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24013    else {
24014        panic!("Failed to get buffer for selection position");
24015    };
24016    let buffer = buffer.read(cx);
24017    let buffer_id = buffer.remote_id();
24018    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24019    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24020    let mut linked_ranges = HashMap::default();
24021    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24022    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24023}
24024
24025#[gpui::test]
24026async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24027    init_test(cx, |_| {});
24028
24029    let fs = FakeFs::new(cx.executor());
24030    fs.insert_file(path!("/file.html"), Default::default())
24031        .await;
24032
24033    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24034
24035    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24036    let html_language = Arc::new(Language::new(
24037        LanguageConfig {
24038            name: "HTML".into(),
24039            matcher: LanguageMatcher {
24040                path_suffixes: vec!["html".to_string()],
24041                ..LanguageMatcher::default()
24042            },
24043            brackets: BracketPairConfig {
24044                pairs: vec![BracketPair {
24045                    start: "<".into(),
24046                    end: ">".into(),
24047                    close: true,
24048                    ..Default::default()
24049                }],
24050                ..Default::default()
24051            },
24052            ..Default::default()
24053        },
24054        Some(tree_sitter_html::LANGUAGE.into()),
24055    ));
24056    language_registry.add(html_language);
24057    let mut fake_servers = language_registry.register_fake_lsp(
24058        "HTML",
24059        FakeLspAdapter {
24060            capabilities: lsp::ServerCapabilities {
24061                completion_provider: Some(lsp::CompletionOptions {
24062                    resolve_provider: Some(true),
24063                    ..Default::default()
24064                }),
24065                ..Default::default()
24066            },
24067            ..Default::default()
24068        },
24069    );
24070
24071    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24072    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24073
24074    let worktree_id = workspace
24075        .update(cx, |workspace, _window, cx| {
24076            workspace.project().update(cx, |project, cx| {
24077                project.worktrees(cx).next().unwrap().read(cx).id()
24078            })
24079        })
24080        .unwrap();
24081    project
24082        .update(cx, |project, cx| {
24083            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24084        })
24085        .await
24086        .unwrap();
24087    let editor = workspace
24088        .update(cx, |workspace, window, cx| {
24089            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24090        })
24091        .unwrap()
24092        .await
24093        .unwrap()
24094        .downcast::<Editor>()
24095        .unwrap();
24096
24097    let fake_server = fake_servers.next().await.unwrap();
24098    editor.update_in(cx, |editor, window, cx| {
24099        editor.set_text("<ad></ad>", window, cx);
24100        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24101            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24102        });
24103        set_linked_edit_ranges(
24104            (Point::new(0, 1), Point::new(0, 3)),
24105            (Point::new(0, 6), Point::new(0, 8)),
24106            editor,
24107            cx,
24108        );
24109    });
24110    let mut completion_handle =
24111        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24112            Ok(Some(lsp::CompletionResponse::Array(vec![
24113                lsp::CompletionItem {
24114                    label: "head".to_string(),
24115                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24116                        lsp::InsertReplaceEdit {
24117                            new_text: "head".to_string(),
24118                            insert: lsp::Range::new(
24119                                lsp::Position::new(0, 1),
24120                                lsp::Position::new(0, 3),
24121                            ),
24122                            replace: lsp::Range::new(
24123                                lsp::Position::new(0, 1),
24124                                lsp::Position::new(0, 3),
24125                            ),
24126                        },
24127                    )),
24128                    ..Default::default()
24129                },
24130            ])))
24131        });
24132    editor.update_in(cx, |editor, window, cx| {
24133        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
24134    });
24135    cx.run_until_parked();
24136    completion_handle.next().await.unwrap();
24137    editor.update(cx, |editor, _| {
24138        assert!(
24139            editor.context_menu_visible(),
24140            "Completion menu should be visible"
24141        );
24142    });
24143    editor.update_in(cx, |editor, window, cx| {
24144        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24145    });
24146    cx.executor().run_until_parked();
24147    editor.update(cx, |editor, cx| {
24148        assert_eq!(editor.text(cx), "<head></head>");
24149    });
24150}
24151
24152#[gpui::test]
24153async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24154    init_test(cx, |_| {});
24155
24156    let mut cx = EditorTestContext::new(cx).await;
24157    let language = Arc::new(Language::new(
24158        LanguageConfig {
24159            name: "TSX".into(),
24160            matcher: LanguageMatcher {
24161                path_suffixes: vec!["tsx".to_string()],
24162                ..LanguageMatcher::default()
24163            },
24164            brackets: BracketPairConfig {
24165                pairs: vec![BracketPair {
24166                    start: "<".into(),
24167                    end: ">".into(),
24168                    close: true,
24169                    ..Default::default()
24170                }],
24171                ..Default::default()
24172            },
24173            linked_edit_characters: HashSet::from_iter(['.']),
24174            ..Default::default()
24175        },
24176        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24177    ));
24178    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24179
24180    // Test typing > does not extend linked pair
24181    cx.set_state("<divˇ<div></div>");
24182    cx.update_editor(|editor, _, cx| {
24183        set_linked_edit_ranges(
24184            (Point::new(0, 1), Point::new(0, 4)),
24185            (Point::new(0, 11), Point::new(0, 14)),
24186            editor,
24187            cx,
24188        );
24189    });
24190    cx.update_editor(|editor, window, cx| {
24191        editor.handle_input(">", window, cx);
24192    });
24193    cx.assert_editor_state("<div>ˇ<div></div>");
24194
24195    // Test typing . do extend linked pair
24196    cx.set_state("<Animatedˇ></Animated>");
24197    cx.update_editor(|editor, _, cx| {
24198        set_linked_edit_ranges(
24199            (Point::new(0, 1), Point::new(0, 9)),
24200            (Point::new(0, 12), Point::new(0, 20)),
24201            editor,
24202            cx,
24203        );
24204    });
24205    cx.update_editor(|editor, window, cx| {
24206        editor.handle_input(".", window, cx);
24207    });
24208    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24209    cx.update_editor(|editor, _, cx| {
24210        set_linked_edit_ranges(
24211            (Point::new(0, 1), Point::new(0, 10)),
24212            (Point::new(0, 13), Point::new(0, 21)),
24213            editor,
24214            cx,
24215        );
24216    });
24217    cx.update_editor(|editor, window, cx| {
24218        editor.handle_input("V", window, cx);
24219    });
24220    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24221}
24222
24223#[gpui::test]
24224async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24225    init_test(cx, |_| {});
24226
24227    let fs = FakeFs::new(cx.executor());
24228    fs.insert_tree(
24229        path!("/root"),
24230        json!({
24231            "a": {
24232                "main.rs": "fn main() {}",
24233            },
24234            "foo": {
24235                "bar": {
24236                    "external_file.rs": "pub mod external {}",
24237                }
24238            }
24239        }),
24240    )
24241    .await;
24242
24243    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24244    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24245    language_registry.add(rust_lang());
24246    let _fake_servers = language_registry.register_fake_lsp(
24247        "Rust",
24248        FakeLspAdapter {
24249            ..FakeLspAdapter::default()
24250        },
24251    );
24252    let (workspace, cx) =
24253        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24254    let worktree_id = workspace.update(cx, |workspace, cx| {
24255        workspace.project().update(cx, |project, cx| {
24256            project.worktrees(cx).next().unwrap().read(cx).id()
24257        })
24258    });
24259
24260    let assert_language_servers_count =
24261        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24262            project.update(cx, |project, cx| {
24263                let current = project
24264                    .lsp_store()
24265                    .read(cx)
24266                    .as_local()
24267                    .unwrap()
24268                    .language_servers
24269                    .len();
24270                assert_eq!(expected, current, "{context}");
24271            });
24272        };
24273
24274    assert_language_servers_count(
24275        0,
24276        "No servers should be running before any file is open",
24277        cx,
24278    );
24279    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24280    let main_editor = workspace
24281        .update_in(cx, |workspace, window, cx| {
24282            workspace.open_path(
24283                (worktree_id, rel_path("main.rs")),
24284                Some(pane.downgrade()),
24285                true,
24286                window,
24287                cx,
24288            )
24289        })
24290        .unwrap()
24291        .await
24292        .downcast::<Editor>()
24293        .unwrap();
24294    pane.update(cx, |pane, cx| {
24295        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24296        open_editor.update(cx, |editor, cx| {
24297            assert_eq!(
24298                editor.display_text(cx),
24299                "fn main() {}",
24300                "Original main.rs text on initial open",
24301            );
24302        });
24303        assert_eq!(open_editor, main_editor);
24304    });
24305    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24306
24307    let external_editor = workspace
24308        .update_in(cx, |workspace, window, cx| {
24309            workspace.open_abs_path(
24310                PathBuf::from("/root/foo/bar/external_file.rs"),
24311                OpenOptions::default(),
24312                window,
24313                cx,
24314            )
24315        })
24316        .await
24317        .expect("opening external file")
24318        .downcast::<Editor>()
24319        .expect("downcasted external file's open element to editor");
24320    pane.update(cx, |pane, cx| {
24321        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24322        open_editor.update(cx, |editor, cx| {
24323            assert_eq!(
24324                editor.display_text(cx),
24325                "pub mod external {}",
24326                "External file is open now",
24327            );
24328        });
24329        assert_eq!(open_editor, external_editor);
24330    });
24331    assert_language_servers_count(
24332        1,
24333        "Second, external, *.rs file should join the existing server",
24334        cx,
24335    );
24336
24337    pane.update_in(cx, |pane, window, cx| {
24338        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24339    })
24340    .await
24341    .unwrap();
24342    pane.update_in(cx, |pane, window, cx| {
24343        pane.navigate_backward(&Default::default(), window, cx);
24344    });
24345    cx.run_until_parked();
24346    pane.update(cx, |pane, cx| {
24347        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24348        open_editor.update(cx, |editor, cx| {
24349            assert_eq!(
24350                editor.display_text(cx),
24351                "pub mod external {}",
24352                "External file is open now",
24353            );
24354        });
24355    });
24356    assert_language_servers_count(
24357        1,
24358        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24359        cx,
24360    );
24361
24362    cx.update(|_, cx| {
24363        workspace::reload(cx);
24364    });
24365    assert_language_servers_count(
24366        1,
24367        "After reloading the worktree with local and external files opened, only one project should be started",
24368        cx,
24369    );
24370}
24371
24372#[gpui::test]
24373async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24374    init_test(cx, |_| {});
24375
24376    let mut cx = EditorTestContext::new(cx).await;
24377    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24378    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24379
24380    // test cursor move to start of each line on tab
24381    // for `if`, `elif`, `else`, `while`, `with` and `for`
24382    cx.set_state(indoc! {"
24383        def main():
24384        ˇ    for item in items:
24385        ˇ        while item.active:
24386        ˇ            if item.value > 10:
24387        ˇ                continue
24388        ˇ            elif item.value < 0:
24389        ˇ                break
24390        ˇ            else:
24391        ˇ                with item.context() as ctx:
24392        ˇ                    yield count
24393        ˇ        else:
24394        ˇ            log('while else')
24395        ˇ    else:
24396        ˇ        log('for else')
24397    "});
24398    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24399    cx.assert_editor_state(indoc! {"
24400        def main():
24401            ˇfor item in items:
24402                ˇwhile item.active:
24403                    ˇif item.value > 10:
24404                        ˇcontinue
24405                    ˇelif item.value < 0:
24406                        ˇbreak
24407                    ˇelse:
24408                        ˇwith item.context() as ctx:
24409                            ˇyield count
24410                ˇelse:
24411                    ˇlog('while else')
24412            ˇelse:
24413                ˇlog('for else')
24414    "});
24415    // test relative indent is preserved when tab
24416    // for `if`, `elif`, `else`, `while`, `with` and `for`
24417    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24418    cx.assert_editor_state(indoc! {"
24419        def main():
24420                ˇfor item in items:
24421                    ˇwhile item.active:
24422                        ˇif item.value > 10:
24423                            ˇcontinue
24424                        ˇelif item.value < 0:
24425                            ˇbreak
24426                        ˇelse:
24427                            ˇwith item.context() as ctx:
24428                                ˇyield count
24429                    ˇelse:
24430                        ˇlog('while else')
24431                ˇelse:
24432                    ˇlog('for else')
24433    "});
24434
24435    // test cursor move to start of each line on tab
24436    // for `try`, `except`, `else`, `finally`, `match` and `def`
24437    cx.set_state(indoc! {"
24438        def main():
24439        ˇ    try:
24440        ˇ        fetch()
24441        ˇ    except ValueError:
24442        ˇ        handle_error()
24443        ˇ    else:
24444        ˇ        match value:
24445        ˇ            case _:
24446        ˇ    finally:
24447        ˇ        def status():
24448        ˇ            return 0
24449    "});
24450    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24451    cx.assert_editor_state(indoc! {"
24452        def main():
24453            ˇtry:
24454                ˇfetch()
24455            ˇexcept ValueError:
24456                ˇhandle_error()
24457            ˇelse:
24458                ˇmatch value:
24459                    ˇcase _:
24460            ˇfinally:
24461                ˇdef status():
24462                    ˇreturn 0
24463    "});
24464    // test relative indent is preserved when tab
24465    // for `try`, `except`, `else`, `finally`, `match` and `def`
24466    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24467    cx.assert_editor_state(indoc! {"
24468        def main():
24469                ˇtry:
24470                    ˇfetch()
24471                ˇexcept ValueError:
24472                    ˇhandle_error()
24473                ˇelse:
24474                    ˇmatch value:
24475                        ˇcase _:
24476                ˇfinally:
24477                    ˇdef status():
24478                        ˇreturn 0
24479    "});
24480}
24481
24482#[gpui::test]
24483async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24484    init_test(cx, |_| {});
24485
24486    let mut cx = EditorTestContext::new(cx).await;
24487    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24488    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24489
24490    // test `else` auto outdents when typed inside `if` block
24491    cx.set_state(indoc! {"
24492        def main():
24493            if i == 2:
24494                return
24495                ˇ
24496    "});
24497    cx.update_editor(|editor, window, cx| {
24498        editor.handle_input("else:", window, cx);
24499    });
24500    cx.assert_editor_state(indoc! {"
24501        def main():
24502            if i == 2:
24503                return
24504            else:ˇ
24505    "});
24506
24507    // test `except` auto outdents when typed inside `try` block
24508    cx.set_state(indoc! {"
24509        def main():
24510            try:
24511                i = 2
24512                ˇ
24513    "});
24514    cx.update_editor(|editor, window, cx| {
24515        editor.handle_input("except:", window, cx);
24516    });
24517    cx.assert_editor_state(indoc! {"
24518        def main():
24519            try:
24520                i = 2
24521            except:ˇ
24522    "});
24523
24524    // test `else` auto outdents when typed inside `except` block
24525    cx.set_state(indoc! {"
24526        def main():
24527            try:
24528                i = 2
24529            except:
24530                j = 2
24531                ˇ
24532    "});
24533    cx.update_editor(|editor, window, cx| {
24534        editor.handle_input("else:", window, cx);
24535    });
24536    cx.assert_editor_state(indoc! {"
24537        def main():
24538            try:
24539                i = 2
24540            except:
24541                j = 2
24542            else:ˇ
24543    "});
24544
24545    // test `finally` auto outdents when typed inside `else` block
24546    cx.set_state(indoc! {"
24547        def main():
24548            try:
24549                i = 2
24550            except:
24551                j = 2
24552            else:
24553                k = 2
24554                ˇ
24555    "});
24556    cx.update_editor(|editor, window, cx| {
24557        editor.handle_input("finally:", window, cx);
24558    });
24559    cx.assert_editor_state(indoc! {"
24560        def main():
24561            try:
24562                i = 2
24563            except:
24564                j = 2
24565            else:
24566                k = 2
24567            finally:ˇ
24568    "});
24569
24570    // test `else` does not outdents when typed inside `except` block right after for block
24571    cx.set_state(indoc! {"
24572        def main():
24573            try:
24574                i = 2
24575            except:
24576                for i in range(n):
24577                    pass
24578                ˇ
24579    "});
24580    cx.update_editor(|editor, window, cx| {
24581        editor.handle_input("else:", window, cx);
24582    });
24583    cx.assert_editor_state(indoc! {"
24584        def main():
24585            try:
24586                i = 2
24587            except:
24588                for i in range(n):
24589                    pass
24590                else:ˇ
24591    "});
24592
24593    // test `finally` auto outdents when typed inside `else` block right after for block
24594    cx.set_state(indoc! {"
24595        def main():
24596            try:
24597                i = 2
24598            except:
24599                j = 2
24600            else:
24601                for i in range(n):
24602                    pass
24603                ˇ
24604    "});
24605    cx.update_editor(|editor, window, cx| {
24606        editor.handle_input("finally:", window, cx);
24607    });
24608    cx.assert_editor_state(indoc! {"
24609        def main():
24610            try:
24611                i = 2
24612            except:
24613                j = 2
24614            else:
24615                for i in range(n):
24616                    pass
24617            finally:ˇ
24618    "});
24619
24620    // test `except` outdents to inner "try" block
24621    cx.set_state(indoc! {"
24622        def main():
24623            try:
24624                i = 2
24625                if i == 2:
24626                    try:
24627                        i = 3
24628                        ˇ
24629    "});
24630    cx.update_editor(|editor, window, cx| {
24631        editor.handle_input("except:", window, cx);
24632    });
24633    cx.assert_editor_state(indoc! {"
24634        def main():
24635            try:
24636                i = 2
24637                if i == 2:
24638                    try:
24639                        i = 3
24640                    except:ˇ
24641    "});
24642
24643    // test `except` outdents to outer "try" block
24644    cx.set_state(indoc! {"
24645        def main():
24646            try:
24647                i = 2
24648                if i == 2:
24649                    try:
24650                        i = 3
24651                ˇ
24652    "});
24653    cx.update_editor(|editor, window, cx| {
24654        editor.handle_input("except:", window, cx);
24655    });
24656    cx.assert_editor_state(indoc! {"
24657        def main():
24658            try:
24659                i = 2
24660                if i == 2:
24661                    try:
24662                        i = 3
24663            except:ˇ
24664    "});
24665
24666    // test `else` stays at correct indent when typed after `for` block
24667    cx.set_state(indoc! {"
24668        def main():
24669            for i in range(10):
24670                if i == 3:
24671                    break
24672            ˇ
24673    "});
24674    cx.update_editor(|editor, window, cx| {
24675        editor.handle_input("else:", window, cx);
24676    });
24677    cx.assert_editor_state(indoc! {"
24678        def main():
24679            for i in range(10):
24680                if i == 3:
24681                    break
24682            else:ˇ
24683    "});
24684
24685    // test does not outdent on typing after line with square brackets
24686    cx.set_state(indoc! {"
24687        def f() -> list[str]:
24688            ˇ
24689    "});
24690    cx.update_editor(|editor, window, cx| {
24691        editor.handle_input("a", window, cx);
24692    });
24693    cx.assert_editor_state(indoc! {"
24694        def f() -> list[str]:
2469524696    "});
24697
24698    // test does not outdent on typing : after case keyword
24699    cx.set_state(indoc! {"
24700        match 1:
24701            caseˇ
24702    "});
24703    cx.update_editor(|editor, window, cx| {
24704        editor.handle_input(":", window, cx);
24705    });
24706    cx.assert_editor_state(indoc! {"
24707        match 1:
24708            case:ˇ
24709    "});
24710}
24711
24712#[gpui::test]
24713async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24714    init_test(cx, |_| {});
24715    update_test_language_settings(cx, |settings| {
24716        settings.defaults.extend_comment_on_newline = Some(false);
24717    });
24718    let mut cx = EditorTestContext::new(cx).await;
24719    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24720    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24721
24722    // test correct indent after newline on comment
24723    cx.set_state(indoc! {"
24724        # COMMENT:ˇ
24725    "});
24726    cx.update_editor(|editor, window, cx| {
24727        editor.newline(&Newline, window, cx);
24728    });
24729    cx.assert_editor_state(indoc! {"
24730        # COMMENT:
24731        ˇ
24732    "});
24733
24734    // test correct indent after newline in brackets
24735    cx.set_state(indoc! {"
24736        {ˇ}
24737    "});
24738    cx.update_editor(|editor, window, cx| {
24739        editor.newline(&Newline, window, cx);
24740    });
24741    cx.run_until_parked();
24742    cx.assert_editor_state(indoc! {"
24743        {
24744            ˇ
24745        }
24746    "});
24747
24748    cx.set_state(indoc! {"
24749        (ˇ)
24750    "});
24751    cx.update_editor(|editor, window, cx| {
24752        editor.newline(&Newline, window, cx);
24753    });
24754    cx.run_until_parked();
24755    cx.assert_editor_state(indoc! {"
24756        (
24757            ˇ
24758        )
24759    "});
24760
24761    // do not indent after empty lists or dictionaries
24762    cx.set_state(indoc! {"
24763        a = []ˇ
24764    "});
24765    cx.update_editor(|editor, window, cx| {
24766        editor.newline(&Newline, window, cx);
24767    });
24768    cx.run_until_parked();
24769    cx.assert_editor_state(indoc! {"
24770        a = []
24771        ˇ
24772    "});
24773}
24774
24775#[gpui::test]
24776async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24777    init_test(cx, |_| {});
24778
24779    let mut cx = EditorTestContext::new(cx).await;
24780    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24781    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24782
24783    // test cursor move to start of each line on tab
24784    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24785    cx.set_state(indoc! {"
24786        function main() {
24787        ˇ    for item in $items; do
24788        ˇ        while [ -n \"$item\" ]; do
24789        ˇ            if [ \"$value\" -gt 10 ]; then
24790        ˇ                continue
24791        ˇ            elif [ \"$value\" -lt 0 ]; then
24792        ˇ                break
24793        ˇ            else
24794        ˇ                echo \"$item\"
24795        ˇ            fi
24796        ˇ        done
24797        ˇ    done
24798        ˇ}
24799    "});
24800    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24801    cx.assert_editor_state(indoc! {"
24802        function main() {
24803            ˇfor item in $items; do
24804                ˇwhile [ -n \"$item\" ]; do
24805                    ˇif [ \"$value\" -gt 10 ]; then
24806                        ˇcontinue
24807                    ˇelif [ \"$value\" -lt 0 ]; then
24808                        ˇbreak
24809                    ˇelse
24810                        ˇecho \"$item\"
24811                    ˇfi
24812                ˇdone
24813            ˇdone
24814        ˇ}
24815    "});
24816    // test relative indent is preserved when tab
24817    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24818    cx.assert_editor_state(indoc! {"
24819        function main() {
24820                ˇfor item in $items; do
24821                    ˇwhile [ -n \"$item\" ]; do
24822                        ˇif [ \"$value\" -gt 10 ]; then
24823                            ˇcontinue
24824                        ˇelif [ \"$value\" -lt 0 ]; then
24825                            ˇbreak
24826                        ˇelse
24827                            ˇecho \"$item\"
24828                        ˇfi
24829                    ˇdone
24830                ˇdone
24831            ˇ}
24832    "});
24833
24834    // test cursor move to start of each line on tab
24835    // for `case` statement with patterns
24836    cx.set_state(indoc! {"
24837        function handle() {
24838        ˇ    case \"$1\" in
24839        ˇ        start)
24840        ˇ            echo \"a\"
24841        ˇ            ;;
24842        ˇ        stop)
24843        ˇ            echo \"b\"
24844        ˇ            ;;
24845        ˇ        *)
24846        ˇ            echo \"c\"
24847        ˇ            ;;
24848        ˇ    esac
24849        ˇ}
24850    "});
24851    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24852    cx.assert_editor_state(indoc! {"
24853        function handle() {
24854            ˇcase \"$1\" in
24855                ˇstart)
24856                    ˇecho \"a\"
24857                    ˇ;;
24858                ˇstop)
24859                    ˇecho \"b\"
24860                    ˇ;;
24861                ˇ*)
24862                    ˇecho \"c\"
24863                    ˇ;;
24864            ˇesac
24865        ˇ}
24866    "});
24867}
24868
24869#[gpui::test]
24870async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24871    init_test(cx, |_| {});
24872
24873    let mut cx = EditorTestContext::new(cx).await;
24874    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24875    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24876
24877    // test indents on comment insert
24878    cx.set_state(indoc! {"
24879        function main() {
24880        ˇ    for item in $items; do
24881        ˇ        while [ -n \"$item\" ]; do
24882        ˇ            if [ \"$value\" -gt 10 ]; then
24883        ˇ                continue
24884        ˇ            elif [ \"$value\" -lt 0 ]; then
24885        ˇ                break
24886        ˇ            else
24887        ˇ                echo \"$item\"
24888        ˇ            fi
24889        ˇ        done
24890        ˇ    done
24891        ˇ}
24892    "});
24893    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24894    cx.assert_editor_state(indoc! {"
24895        function main() {
24896        #ˇ    for item in $items; do
24897        #ˇ        while [ -n \"$item\" ]; do
24898        #ˇ            if [ \"$value\" -gt 10 ]; then
24899        #ˇ                continue
24900        #ˇ            elif [ \"$value\" -lt 0 ]; then
24901        #ˇ                break
24902        #ˇ            else
24903        #ˇ                echo \"$item\"
24904        #ˇ            fi
24905        #ˇ        done
24906        #ˇ    done
24907        #ˇ}
24908    "});
24909}
24910
24911#[gpui::test]
24912async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24913    init_test(cx, |_| {});
24914
24915    let mut cx = EditorTestContext::new(cx).await;
24916    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24917    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24918
24919    // test `else` auto outdents when typed inside `if` block
24920    cx.set_state(indoc! {"
24921        if [ \"$1\" = \"test\" ]; then
24922            echo \"foo bar\"
24923            ˇ
24924    "});
24925    cx.update_editor(|editor, window, cx| {
24926        editor.handle_input("else", window, cx);
24927    });
24928    cx.assert_editor_state(indoc! {"
24929        if [ \"$1\" = \"test\" ]; then
24930            echo \"foo bar\"
24931        elseˇ
24932    "});
24933
24934    // test `elif` auto outdents when typed inside `if` block
24935    cx.set_state(indoc! {"
24936        if [ \"$1\" = \"test\" ]; then
24937            echo \"foo bar\"
24938            ˇ
24939    "});
24940    cx.update_editor(|editor, window, cx| {
24941        editor.handle_input("elif", window, cx);
24942    });
24943    cx.assert_editor_state(indoc! {"
24944        if [ \"$1\" = \"test\" ]; then
24945            echo \"foo bar\"
24946        elifˇ
24947    "});
24948
24949    // test `fi` auto outdents when typed inside `else` block
24950    cx.set_state(indoc! {"
24951        if [ \"$1\" = \"test\" ]; then
24952            echo \"foo bar\"
24953        else
24954            echo \"bar baz\"
24955            ˇ
24956    "});
24957    cx.update_editor(|editor, window, cx| {
24958        editor.handle_input("fi", window, cx);
24959    });
24960    cx.assert_editor_state(indoc! {"
24961        if [ \"$1\" = \"test\" ]; then
24962            echo \"foo bar\"
24963        else
24964            echo \"bar baz\"
24965        fiˇ
24966    "});
24967
24968    // test `done` auto outdents when typed inside `while` block
24969    cx.set_state(indoc! {"
24970        while read line; do
24971            echo \"$line\"
24972            ˇ
24973    "});
24974    cx.update_editor(|editor, window, cx| {
24975        editor.handle_input("done", window, cx);
24976    });
24977    cx.assert_editor_state(indoc! {"
24978        while read line; do
24979            echo \"$line\"
24980        doneˇ
24981    "});
24982
24983    // test `done` auto outdents when typed inside `for` block
24984    cx.set_state(indoc! {"
24985        for file in *.txt; do
24986            cat \"$file\"
24987            ˇ
24988    "});
24989    cx.update_editor(|editor, window, cx| {
24990        editor.handle_input("done", window, cx);
24991    });
24992    cx.assert_editor_state(indoc! {"
24993        for file in *.txt; do
24994            cat \"$file\"
24995        doneˇ
24996    "});
24997
24998    // test `esac` auto outdents when typed inside `case` block
24999    cx.set_state(indoc! {"
25000        case \"$1\" in
25001            start)
25002                echo \"foo bar\"
25003                ;;
25004            stop)
25005                echo \"bar baz\"
25006                ;;
25007            ˇ
25008    "});
25009    cx.update_editor(|editor, window, cx| {
25010        editor.handle_input("esac", window, cx);
25011    });
25012    cx.assert_editor_state(indoc! {"
25013        case \"$1\" in
25014            start)
25015                echo \"foo bar\"
25016                ;;
25017            stop)
25018                echo \"bar baz\"
25019                ;;
25020        esacˇ
25021    "});
25022
25023    // test `*)` auto outdents when typed inside `case` block
25024    cx.set_state(indoc! {"
25025        case \"$1\" in
25026            start)
25027                echo \"foo bar\"
25028                ;;
25029                ˇ
25030    "});
25031    cx.update_editor(|editor, window, cx| {
25032        editor.handle_input("*)", window, cx);
25033    });
25034    cx.assert_editor_state(indoc! {"
25035        case \"$1\" in
25036            start)
25037                echo \"foo bar\"
25038                ;;
25039            *)ˇ
25040    "});
25041
25042    // test `fi` outdents to correct level with nested if blocks
25043    cx.set_state(indoc! {"
25044        if [ \"$1\" = \"test\" ]; then
25045            echo \"outer if\"
25046            if [ \"$2\" = \"debug\" ]; then
25047                echo \"inner if\"
25048                ˇ
25049    "});
25050    cx.update_editor(|editor, window, cx| {
25051        editor.handle_input("fi", window, cx);
25052    });
25053    cx.assert_editor_state(indoc! {"
25054        if [ \"$1\" = \"test\" ]; then
25055            echo \"outer if\"
25056            if [ \"$2\" = \"debug\" ]; then
25057                echo \"inner if\"
25058            fiˇ
25059    "});
25060}
25061
25062#[gpui::test]
25063async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25064    init_test(cx, |_| {});
25065    update_test_language_settings(cx, |settings| {
25066        settings.defaults.extend_comment_on_newline = Some(false);
25067    });
25068    let mut cx = EditorTestContext::new(cx).await;
25069    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25070    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25071
25072    // test correct indent after newline on comment
25073    cx.set_state(indoc! {"
25074        # COMMENT:ˇ
25075    "});
25076    cx.update_editor(|editor, window, cx| {
25077        editor.newline(&Newline, window, cx);
25078    });
25079    cx.assert_editor_state(indoc! {"
25080        # COMMENT:
25081        ˇ
25082    "});
25083
25084    // test correct indent after newline after `then`
25085    cx.set_state(indoc! {"
25086
25087        if [ \"$1\" = \"test\" ]; thenˇ
25088    "});
25089    cx.update_editor(|editor, window, cx| {
25090        editor.newline(&Newline, window, cx);
25091    });
25092    cx.run_until_parked();
25093    cx.assert_editor_state(indoc! {"
25094
25095        if [ \"$1\" = \"test\" ]; then
25096            ˇ
25097    "});
25098
25099    // test correct indent after newline after `else`
25100    cx.set_state(indoc! {"
25101        if [ \"$1\" = \"test\" ]; then
25102        elseˇ
25103    "});
25104    cx.update_editor(|editor, window, cx| {
25105        editor.newline(&Newline, window, cx);
25106    });
25107    cx.run_until_parked();
25108    cx.assert_editor_state(indoc! {"
25109        if [ \"$1\" = \"test\" ]; then
25110        else
25111            ˇ
25112    "});
25113
25114    // test correct indent after newline after `elif`
25115    cx.set_state(indoc! {"
25116        if [ \"$1\" = \"test\" ]; then
25117        elifˇ
25118    "});
25119    cx.update_editor(|editor, window, cx| {
25120        editor.newline(&Newline, window, cx);
25121    });
25122    cx.run_until_parked();
25123    cx.assert_editor_state(indoc! {"
25124        if [ \"$1\" = \"test\" ]; then
25125        elif
25126            ˇ
25127    "});
25128
25129    // test correct indent after newline after `do`
25130    cx.set_state(indoc! {"
25131        for file in *.txt; doˇ
25132    "});
25133    cx.update_editor(|editor, window, cx| {
25134        editor.newline(&Newline, window, cx);
25135    });
25136    cx.run_until_parked();
25137    cx.assert_editor_state(indoc! {"
25138        for file in *.txt; do
25139            ˇ
25140    "});
25141
25142    // test correct indent after newline after case pattern
25143    cx.set_state(indoc! {"
25144        case \"$1\" in
25145            start)ˇ
25146    "});
25147    cx.update_editor(|editor, window, cx| {
25148        editor.newline(&Newline, window, cx);
25149    });
25150    cx.run_until_parked();
25151    cx.assert_editor_state(indoc! {"
25152        case \"$1\" in
25153            start)
25154                ˇ
25155    "});
25156
25157    // test correct indent after newline after case pattern
25158    cx.set_state(indoc! {"
25159        case \"$1\" in
25160            start)
25161                ;;
25162            *)ˇ
25163    "});
25164    cx.update_editor(|editor, window, cx| {
25165        editor.newline(&Newline, window, cx);
25166    });
25167    cx.run_until_parked();
25168    cx.assert_editor_state(indoc! {"
25169        case \"$1\" in
25170            start)
25171                ;;
25172            *)
25173                ˇ
25174    "});
25175
25176    // test correct indent after newline after function opening brace
25177    cx.set_state(indoc! {"
25178        function test() {ˇ}
25179    "});
25180    cx.update_editor(|editor, window, cx| {
25181        editor.newline(&Newline, window, cx);
25182    });
25183    cx.run_until_parked();
25184    cx.assert_editor_state(indoc! {"
25185        function test() {
25186            ˇ
25187        }
25188    "});
25189
25190    // test no extra indent after semicolon on same line
25191    cx.set_state(indoc! {"
25192        echo \"test\"25193    "});
25194    cx.update_editor(|editor, window, cx| {
25195        editor.newline(&Newline, window, cx);
25196    });
25197    cx.run_until_parked();
25198    cx.assert_editor_state(indoc! {"
25199        echo \"test\";
25200        ˇ
25201    "});
25202}
25203
25204fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25205    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25206    point..point
25207}
25208
25209#[track_caller]
25210fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25211    let (text, ranges) = marked_text_ranges(marked_text, true);
25212    assert_eq!(editor.text(cx), text);
25213    assert_eq!(
25214        editor.selections.ranges(cx),
25215        ranges,
25216        "Assert selections are {}",
25217        marked_text
25218    );
25219}
25220
25221pub fn handle_signature_help_request(
25222    cx: &mut EditorLspTestContext,
25223    mocked_response: lsp::SignatureHelp,
25224) -> impl Future<Output = ()> + use<> {
25225    let mut request =
25226        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25227            let mocked_response = mocked_response.clone();
25228            async move { Ok(Some(mocked_response)) }
25229        });
25230
25231    async move {
25232        request.next().await;
25233    }
25234}
25235
25236#[track_caller]
25237pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25238    cx.update_editor(|editor, _, _| {
25239        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25240            let entries = menu.entries.borrow();
25241            let entries = entries
25242                .iter()
25243                .map(|entry| entry.string.as_str())
25244                .collect::<Vec<_>>();
25245            assert_eq!(entries, expected);
25246        } else {
25247            panic!("Expected completions menu");
25248        }
25249    });
25250}
25251
25252/// Handle completion request passing a marked string specifying where the completion
25253/// should be triggered from using '|' character, what range should be replaced, and what completions
25254/// should be returned using '<' and '>' to delimit the range.
25255///
25256/// Also see `handle_completion_request_with_insert_and_replace`.
25257#[track_caller]
25258pub fn handle_completion_request(
25259    marked_string: &str,
25260    completions: Vec<&'static str>,
25261    is_incomplete: bool,
25262    counter: Arc<AtomicUsize>,
25263    cx: &mut EditorLspTestContext,
25264) -> impl Future<Output = ()> {
25265    let complete_from_marker: TextRangeMarker = '|'.into();
25266    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25267    let (_, mut marked_ranges) = marked_text_ranges_by(
25268        marked_string,
25269        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25270    );
25271
25272    let complete_from_position =
25273        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25274    let replace_range =
25275        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25276
25277    let mut request =
25278        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25279            let completions = completions.clone();
25280            counter.fetch_add(1, atomic::Ordering::Release);
25281            async move {
25282                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25283                assert_eq!(
25284                    params.text_document_position.position,
25285                    complete_from_position
25286                );
25287                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25288                    is_incomplete,
25289                    item_defaults: None,
25290                    items: completions
25291                        .iter()
25292                        .map(|completion_text| lsp::CompletionItem {
25293                            label: completion_text.to_string(),
25294                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25295                                range: replace_range,
25296                                new_text: completion_text.to_string(),
25297                            })),
25298                            ..Default::default()
25299                        })
25300                        .collect(),
25301                })))
25302            }
25303        });
25304
25305    async move {
25306        request.next().await;
25307    }
25308}
25309
25310/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25311/// given instead, which also contains an `insert` range.
25312///
25313/// This function uses markers to define ranges:
25314/// - `|` marks the cursor position
25315/// - `<>` marks the replace range
25316/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25317pub fn handle_completion_request_with_insert_and_replace(
25318    cx: &mut EditorLspTestContext,
25319    marked_string: &str,
25320    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25321    counter: Arc<AtomicUsize>,
25322) -> impl Future<Output = ()> {
25323    let complete_from_marker: TextRangeMarker = '|'.into();
25324    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25325    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25326
25327    let (_, mut marked_ranges) = marked_text_ranges_by(
25328        marked_string,
25329        vec![
25330            complete_from_marker.clone(),
25331            replace_range_marker.clone(),
25332            insert_range_marker.clone(),
25333        ],
25334    );
25335
25336    let complete_from_position =
25337        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25338    let replace_range =
25339        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25340
25341    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25342        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25343        _ => lsp::Range {
25344            start: replace_range.start,
25345            end: complete_from_position,
25346        },
25347    };
25348
25349    let mut request =
25350        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25351            let completions = completions.clone();
25352            counter.fetch_add(1, atomic::Ordering::Release);
25353            async move {
25354                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25355                assert_eq!(
25356                    params.text_document_position.position, complete_from_position,
25357                    "marker `|` position doesn't match",
25358                );
25359                Ok(Some(lsp::CompletionResponse::Array(
25360                    completions
25361                        .iter()
25362                        .map(|(label, new_text)| lsp::CompletionItem {
25363                            label: label.to_string(),
25364                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25365                                lsp::InsertReplaceEdit {
25366                                    insert: insert_range,
25367                                    replace: replace_range,
25368                                    new_text: new_text.to_string(),
25369                                },
25370                            )),
25371                            ..Default::default()
25372                        })
25373                        .collect(),
25374                )))
25375            }
25376        });
25377
25378    async move {
25379        request.next().await;
25380    }
25381}
25382
25383fn handle_resolve_completion_request(
25384    cx: &mut EditorLspTestContext,
25385    edits: Option<Vec<(&'static str, &'static str)>>,
25386) -> impl Future<Output = ()> {
25387    let edits = edits.map(|edits| {
25388        edits
25389            .iter()
25390            .map(|(marked_string, new_text)| {
25391                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25392                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25393                lsp::TextEdit::new(replace_range, new_text.to_string())
25394            })
25395            .collect::<Vec<_>>()
25396    });
25397
25398    let mut request =
25399        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25400            let edits = edits.clone();
25401            async move {
25402                Ok(lsp::CompletionItem {
25403                    additional_text_edits: edits,
25404                    ..Default::default()
25405                })
25406            }
25407        });
25408
25409    async move {
25410        request.next().await;
25411    }
25412}
25413
25414pub(crate) fn update_test_language_settings(
25415    cx: &mut TestAppContext,
25416    f: impl Fn(&mut AllLanguageSettingsContent),
25417) {
25418    cx.update(|cx| {
25419        SettingsStore::update_global(cx, |store, cx| {
25420            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25421        });
25422    });
25423}
25424
25425pub(crate) fn update_test_project_settings(
25426    cx: &mut TestAppContext,
25427    f: impl Fn(&mut ProjectSettingsContent),
25428) {
25429    cx.update(|cx| {
25430        SettingsStore::update_global(cx, |store, cx| {
25431            store.update_user_settings(cx, |settings| f(&mut settings.project));
25432        });
25433    });
25434}
25435
25436pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25437    cx.update(|cx| {
25438        assets::Assets.load_test_fonts(cx);
25439        let store = SettingsStore::test(cx);
25440        cx.set_global(store);
25441        theme::init(theme::LoadThemes::JustBase, cx);
25442        release_channel::init(SemanticVersion::default(), cx);
25443        client::init_settings(cx);
25444        language::init(cx);
25445        Project::init_settings(cx);
25446        workspace::init_settings(cx);
25447        crate::init(cx);
25448    });
25449    zlog::init_test();
25450    update_test_language_settings(cx, f);
25451}
25452
25453#[track_caller]
25454fn assert_hunk_revert(
25455    not_reverted_text_with_selections: &str,
25456    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25457    expected_reverted_text_with_selections: &str,
25458    base_text: &str,
25459    cx: &mut EditorLspTestContext,
25460) {
25461    cx.set_state(not_reverted_text_with_selections);
25462    cx.set_head_text(base_text);
25463    cx.executor().run_until_parked();
25464
25465    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25466        let snapshot = editor.snapshot(window, cx);
25467        let reverted_hunk_statuses = snapshot
25468            .buffer_snapshot()
25469            .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
25470            .map(|hunk| hunk.status().kind)
25471            .collect::<Vec<_>>();
25472
25473        editor.git_restore(&Default::default(), window, cx);
25474        reverted_hunk_statuses
25475    });
25476    cx.executor().run_until_parked();
25477    cx.assert_editor_state(expected_reverted_text_with_selections);
25478    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25479}
25480
25481#[gpui::test(iterations = 10)]
25482async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25483    init_test(cx, |_| {});
25484
25485    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25486    let counter = diagnostic_requests.clone();
25487
25488    let fs = FakeFs::new(cx.executor());
25489    fs.insert_tree(
25490        path!("/a"),
25491        json!({
25492            "first.rs": "fn main() { let a = 5; }",
25493            "second.rs": "// Test file",
25494        }),
25495    )
25496    .await;
25497
25498    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25499    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25500    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25501
25502    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25503    language_registry.add(rust_lang());
25504    let mut fake_servers = language_registry.register_fake_lsp(
25505        "Rust",
25506        FakeLspAdapter {
25507            capabilities: lsp::ServerCapabilities {
25508                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25509                    lsp::DiagnosticOptions {
25510                        identifier: None,
25511                        inter_file_dependencies: true,
25512                        workspace_diagnostics: true,
25513                        work_done_progress_options: Default::default(),
25514                    },
25515                )),
25516                ..Default::default()
25517            },
25518            ..Default::default()
25519        },
25520    );
25521
25522    let editor = workspace
25523        .update(cx, |workspace, window, cx| {
25524            workspace.open_abs_path(
25525                PathBuf::from(path!("/a/first.rs")),
25526                OpenOptions::default(),
25527                window,
25528                cx,
25529            )
25530        })
25531        .unwrap()
25532        .await
25533        .unwrap()
25534        .downcast::<Editor>()
25535        .unwrap();
25536    let fake_server = fake_servers.next().await.unwrap();
25537    let server_id = fake_server.server.server_id();
25538    let mut first_request = fake_server
25539        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25540            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25541            let result_id = Some(new_result_id.to_string());
25542            assert_eq!(
25543                params.text_document.uri,
25544                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25545            );
25546            async move {
25547                Ok(lsp::DocumentDiagnosticReportResult::Report(
25548                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25549                        related_documents: None,
25550                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25551                            items: Vec::new(),
25552                            result_id,
25553                        },
25554                    }),
25555                ))
25556            }
25557        });
25558
25559    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25560        project.update(cx, |project, cx| {
25561            let buffer_id = editor
25562                .read(cx)
25563                .buffer()
25564                .read(cx)
25565                .as_singleton()
25566                .expect("created a singleton buffer")
25567                .read(cx)
25568                .remote_id();
25569            let buffer_result_id = project
25570                .lsp_store()
25571                .read(cx)
25572                .result_id(server_id, buffer_id, cx);
25573            assert_eq!(expected, buffer_result_id);
25574        });
25575    };
25576
25577    ensure_result_id(None, cx);
25578    cx.executor().advance_clock(Duration::from_millis(60));
25579    cx.executor().run_until_parked();
25580    assert_eq!(
25581        diagnostic_requests.load(atomic::Ordering::Acquire),
25582        1,
25583        "Opening file should trigger diagnostic request"
25584    );
25585    first_request
25586        .next()
25587        .await
25588        .expect("should have sent the first diagnostics pull request");
25589    ensure_result_id(Some("1".to_string()), cx);
25590
25591    // Editing should trigger diagnostics
25592    editor.update_in(cx, |editor, window, cx| {
25593        editor.handle_input("2", window, cx)
25594    });
25595    cx.executor().advance_clock(Duration::from_millis(60));
25596    cx.executor().run_until_parked();
25597    assert_eq!(
25598        diagnostic_requests.load(atomic::Ordering::Acquire),
25599        2,
25600        "Editing should trigger diagnostic request"
25601    );
25602    ensure_result_id(Some("2".to_string()), cx);
25603
25604    // Moving cursor should not trigger diagnostic request
25605    editor.update_in(cx, |editor, window, cx| {
25606        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25607            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25608        });
25609    });
25610    cx.executor().advance_clock(Duration::from_millis(60));
25611    cx.executor().run_until_parked();
25612    assert_eq!(
25613        diagnostic_requests.load(atomic::Ordering::Acquire),
25614        2,
25615        "Cursor movement should not trigger diagnostic request"
25616    );
25617    ensure_result_id(Some("2".to_string()), cx);
25618    // Multiple rapid edits should be debounced
25619    for _ in 0..5 {
25620        editor.update_in(cx, |editor, window, cx| {
25621            editor.handle_input("x", window, cx)
25622        });
25623    }
25624    cx.executor().advance_clock(Duration::from_millis(60));
25625    cx.executor().run_until_parked();
25626
25627    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25628    assert!(
25629        final_requests <= 4,
25630        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25631    );
25632    ensure_result_id(Some(final_requests.to_string()), cx);
25633}
25634
25635#[gpui::test]
25636async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25637    // Regression test for issue #11671
25638    // Previously, adding a cursor after moving multiple cursors would reset
25639    // the cursor count instead of adding to the existing cursors.
25640    init_test(cx, |_| {});
25641    let mut cx = EditorTestContext::new(cx).await;
25642
25643    // Create a simple buffer with cursor at start
25644    cx.set_state(indoc! {"
25645        ˇaaaa
25646        bbbb
25647        cccc
25648        dddd
25649        eeee
25650        ffff
25651        gggg
25652        hhhh"});
25653
25654    // Add 2 cursors below (so we have 3 total)
25655    cx.update_editor(|editor, window, cx| {
25656        editor.add_selection_below(&Default::default(), window, cx);
25657        editor.add_selection_below(&Default::default(), window, cx);
25658    });
25659
25660    // Verify we have 3 cursors
25661    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25662    assert_eq!(
25663        initial_count, 3,
25664        "Should have 3 cursors after adding 2 below"
25665    );
25666
25667    // Move down one line
25668    cx.update_editor(|editor, window, cx| {
25669        editor.move_down(&MoveDown, window, cx);
25670    });
25671
25672    // Add another cursor below
25673    cx.update_editor(|editor, window, cx| {
25674        editor.add_selection_below(&Default::default(), window, cx);
25675    });
25676
25677    // Should now have 4 cursors (3 original + 1 new)
25678    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25679    assert_eq!(
25680        final_count, 4,
25681        "Should have 4 cursors after moving and adding another"
25682    );
25683}
25684
25685#[gpui::test]
25686async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
25687    init_test(cx, |_| {});
25688
25689    let mut cx = EditorTestContext::new(cx).await;
25690
25691    cx.set_state(indoc!(
25692        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
25693           Second line here"#
25694    ));
25695
25696    cx.update_editor(|editor, window, cx| {
25697        // Enable soft wrapping with a narrow width to force soft wrapping and
25698        // confirm that more than 2 rows are being displayed.
25699        editor.set_wrap_width(Some(100.0.into()), cx);
25700        assert!(editor.display_text(cx).lines().count() > 2);
25701
25702        editor.add_selection_below(
25703            &AddSelectionBelow {
25704                skip_soft_wrap: true,
25705            },
25706            window,
25707            cx,
25708        );
25709
25710        assert_eq!(
25711            editor.selections.display_ranges(cx),
25712            &[
25713                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
25714                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
25715            ]
25716        );
25717
25718        editor.add_selection_above(
25719            &AddSelectionAbove {
25720                skip_soft_wrap: true,
25721            },
25722            window,
25723            cx,
25724        );
25725
25726        assert_eq!(
25727            editor.selections.display_ranges(cx),
25728            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
25729        );
25730
25731        editor.add_selection_below(
25732            &AddSelectionBelow {
25733                skip_soft_wrap: false,
25734            },
25735            window,
25736            cx,
25737        );
25738
25739        assert_eq!(
25740            editor.selections.display_ranges(cx),
25741            &[
25742                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
25743                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
25744            ]
25745        );
25746
25747        editor.add_selection_above(
25748            &AddSelectionAbove {
25749                skip_soft_wrap: false,
25750            },
25751            window,
25752            cx,
25753        );
25754
25755        assert_eq!(
25756            editor.selections.display_ranges(cx),
25757            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
25758        );
25759    });
25760}
25761
25762#[gpui::test(iterations = 10)]
25763async fn test_document_colors(cx: &mut TestAppContext) {
25764    let expected_color = Rgba {
25765        r: 0.33,
25766        g: 0.33,
25767        b: 0.33,
25768        a: 0.33,
25769    };
25770
25771    init_test(cx, |_| {});
25772
25773    let fs = FakeFs::new(cx.executor());
25774    fs.insert_tree(
25775        path!("/a"),
25776        json!({
25777            "first.rs": "fn main() { let a = 5; }",
25778        }),
25779    )
25780    .await;
25781
25782    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25783    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25784    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25785
25786    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25787    language_registry.add(rust_lang());
25788    let mut fake_servers = language_registry.register_fake_lsp(
25789        "Rust",
25790        FakeLspAdapter {
25791            capabilities: lsp::ServerCapabilities {
25792                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25793                ..lsp::ServerCapabilities::default()
25794            },
25795            name: "rust-analyzer",
25796            ..FakeLspAdapter::default()
25797        },
25798    );
25799    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25800        "Rust",
25801        FakeLspAdapter {
25802            capabilities: lsp::ServerCapabilities {
25803                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25804                ..lsp::ServerCapabilities::default()
25805            },
25806            name: "not-rust-analyzer",
25807            ..FakeLspAdapter::default()
25808        },
25809    );
25810
25811    let editor = workspace
25812        .update(cx, |workspace, window, cx| {
25813            workspace.open_abs_path(
25814                PathBuf::from(path!("/a/first.rs")),
25815                OpenOptions::default(),
25816                window,
25817                cx,
25818            )
25819        })
25820        .unwrap()
25821        .await
25822        .unwrap()
25823        .downcast::<Editor>()
25824        .unwrap();
25825    let fake_language_server = fake_servers.next().await.unwrap();
25826    let fake_language_server_without_capabilities =
25827        fake_servers_without_capabilities.next().await.unwrap();
25828    let requests_made = Arc::new(AtomicUsize::new(0));
25829    let closure_requests_made = Arc::clone(&requests_made);
25830    let mut color_request_handle = fake_language_server
25831        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25832            let requests_made = Arc::clone(&closure_requests_made);
25833            async move {
25834                assert_eq!(
25835                    params.text_document.uri,
25836                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25837                );
25838                requests_made.fetch_add(1, atomic::Ordering::Release);
25839                Ok(vec![
25840                    lsp::ColorInformation {
25841                        range: lsp::Range {
25842                            start: lsp::Position {
25843                                line: 0,
25844                                character: 0,
25845                            },
25846                            end: lsp::Position {
25847                                line: 0,
25848                                character: 1,
25849                            },
25850                        },
25851                        color: lsp::Color {
25852                            red: 0.33,
25853                            green: 0.33,
25854                            blue: 0.33,
25855                            alpha: 0.33,
25856                        },
25857                    },
25858                    lsp::ColorInformation {
25859                        range: lsp::Range {
25860                            start: lsp::Position {
25861                                line: 0,
25862                                character: 0,
25863                            },
25864                            end: lsp::Position {
25865                                line: 0,
25866                                character: 1,
25867                            },
25868                        },
25869                        color: lsp::Color {
25870                            red: 0.33,
25871                            green: 0.33,
25872                            blue: 0.33,
25873                            alpha: 0.33,
25874                        },
25875                    },
25876                ])
25877            }
25878        });
25879
25880    let _handle = fake_language_server_without_capabilities
25881        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25882            panic!("Should not be called");
25883        });
25884    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
25885    color_request_handle.next().await.unwrap();
25886    cx.run_until_parked();
25887    assert_eq!(
25888        1,
25889        requests_made.load(atomic::Ordering::Acquire),
25890        "Should query for colors once per editor open"
25891    );
25892    editor.update_in(cx, |editor, _, cx| {
25893        assert_eq!(
25894            vec![expected_color],
25895            extract_color_inlays(editor, cx),
25896            "Should have an initial inlay"
25897        );
25898    });
25899
25900    // opening another file in a split should not influence the LSP query counter
25901    workspace
25902        .update(cx, |workspace, window, cx| {
25903            assert_eq!(
25904                workspace.panes().len(),
25905                1,
25906                "Should have one pane with one editor"
25907            );
25908            workspace.move_item_to_pane_in_direction(
25909                &MoveItemToPaneInDirection {
25910                    direction: SplitDirection::Right,
25911                    focus: false,
25912                    clone: true,
25913                },
25914                window,
25915                cx,
25916            );
25917        })
25918        .unwrap();
25919    cx.run_until_parked();
25920    workspace
25921        .update(cx, |workspace, _, cx| {
25922            let panes = workspace.panes();
25923            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25924            for pane in panes {
25925                let editor = pane
25926                    .read(cx)
25927                    .active_item()
25928                    .and_then(|item| item.downcast::<Editor>())
25929                    .expect("Should have opened an editor in each split");
25930                let editor_file = editor
25931                    .read(cx)
25932                    .buffer()
25933                    .read(cx)
25934                    .as_singleton()
25935                    .expect("test deals with singleton buffers")
25936                    .read(cx)
25937                    .file()
25938                    .expect("test buffese should have a file")
25939                    .path();
25940                assert_eq!(
25941                    editor_file.as_ref(),
25942                    rel_path("first.rs"),
25943                    "Both editors should be opened for the same file"
25944                )
25945            }
25946        })
25947        .unwrap();
25948
25949    cx.executor().advance_clock(Duration::from_millis(500));
25950    let save = editor.update_in(cx, |editor, window, cx| {
25951        editor.move_to_end(&MoveToEnd, window, cx);
25952        editor.handle_input("dirty", window, cx);
25953        editor.save(
25954            SaveOptions {
25955                format: true,
25956                autosave: true,
25957            },
25958            project.clone(),
25959            window,
25960            cx,
25961        )
25962    });
25963    save.await.unwrap();
25964
25965    color_request_handle.next().await.unwrap();
25966    cx.run_until_parked();
25967    assert_eq!(
25968        2,
25969        requests_made.load(atomic::Ordering::Acquire),
25970        "Should query for colors once per save (deduplicated) and once per formatting after save"
25971    );
25972
25973    drop(editor);
25974    let close = workspace
25975        .update(cx, |workspace, window, cx| {
25976            workspace.active_pane().update(cx, |pane, cx| {
25977                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25978            })
25979        })
25980        .unwrap();
25981    close.await.unwrap();
25982    let close = workspace
25983        .update(cx, |workspace, window, cx| {
25984            workspace.active_pane().update(cx, |pane, cx| {
25985                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25986            })
25987        })
25988        .unwrap();
25989    close.await.unwrap();
25990    assert_eq!(
25991        2,
25992        requests_made.load(atomic::Ordering::Acquire),
25993        "After saving and closing all editors, no extra requests should be made"
25994    );
25995    workspace
25996        .update(cx, |workspace, _, cx| {
25997            assert!(
25998                workspace.active_item(cx).is_none(),
25999                "Should close all editors"
26000            )
26001        })
26002        .unwrap();
26003
26004    workspace
26005        .update(cx, |workspace, window, cx| {
26006            workspace.active_pane().update(cx, |pane, cx| {
26007                pane.navigate_backward(&workspace::GoBack, window, cx);
26008            })
26009        })
26010        .unwrap();
26011    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26012    cx.run_until_parked();
26013    let editor = workspace
26014        .update(cx, |workspace, _, cx| {
26015            workspace
26016                .active_item(cx)
26017                .expect("Should have reopened the editor again after navigating back")
26018                .downcast::<Editor>()
26019                .expect("Should be an editor")
26020        })
26021        .unwrap();
26022
26023    assert_eq!(
26024        2,
26025        requests_made.load(atomic::Ordering::Acquire),
26026        "Cache should be reused on buffer close and reopen"
26027    );
26028    editor.update(cx, |editor, cx| {
26029        assert_eq!(
26030            vec![expected_color],
26031            extract_color_inlays(editor, cx),
26032            "Should have an initial inlay"
26033        );
26034    });
26035
26036    drop(color_request_handle);
26037    let closure_requests_made = Arc::clone(&requests_made);
26038    let mut empty_color_request_handle = fake_language_server
26039        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26040            let requests_made = Arc::clone(&closure_requests_made);
26041            async move {
26042                assert_eq!(
26043                    params.text_document.uri,
26044                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26045                );
26046                requests_made.fetch_add(1, atomic::Ordering::Release);
26047                Ok(Vec::new())
26048            }
26049        });
26050    let save = editor.update_in(cx, |editor, window, cx| {
26051        editor.move_to_end(&MoveToEnd, window, cx);
26052        editor.handle_input("dirty_again", window, cx);
26053        editor.save(
26054            SaveOptions {
26055                format: false,
26056                autosave: true,
26057            },
26058            project.clone(),
26059            window,
26060            cx,
26061        )
26062    });
26063    save.await.unwrap();
26064
26065    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26066    empty_color_request_handle.next().await.unwrap();
26067    cx.run_until_parked();
26068    assert_eq!(
26069        3,
26070        requests_made.load(atomic::Ordering::Acquire),
26071        "Should query for colors once per save only, as formatting was not requested"
26072    );
26073    editor.update(cx, |editor, cx| {
26074        assert_eq!(
26075            Vec::<Rgba>::new(),
26076            extract_color_inlays(editor, cx),
26077            "Should clear all colors when the server returns an empty response"
26078        );
26079    });
26080}
26081
26082#[gpui::test]
26083async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
26084    init_test(cx, |_| {});
26085    let (editor, cx) = cx.add_window_view(Editor::single_line);
26086    editor.update_in(cx, |editor, window, cx| {
26087        editor.set_text("oops\n\nwow\n", window, cx)
26088    });
26089    cx.run_until_parked();
26090    editor.update(cx, |editor, cx| {
26091        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
26092    });
26093    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
26094    cx.run_until_parked();
26095    editor.update(cx, |editor, cx| {
26096        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
26097    });
26098}
26099
26100#[gpui::test]
26101async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
26102    init_test(cx, |_| {});
26103
26104    cx.update(|cx| {
26105        register_project_item::<Editor>(cx);
26106    });
26107
26108    let fs = FakeFs::new(cx.executor());
26109    fs.insert_tree("/root1", json!({})).await;
26110    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
26111        .await;
26112
26113    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
26114    let (workspace, cx) =
26115        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26116
26117    let worktree_id = project.update(cx, |project, cx| {
26118        project.worktrees(cx).next().unwrap().read(cx).id()
26119    });
26120
26121    let handle = workspace
26122        .update_in(cx, |workspace, window, cx| {
26123            let project_path = (worktree_id, rel_path("one.pdf"));
26124            workspace.open_path(project_path, None, true, window, cx)
26125        })
26126        .await
26127        .unwrap();
26128
26129    assert_eq!(
26130        handle.to_any().entity_type(),
26131        TypeId::of::<InvalidBufferView>()
26132    );
26133}
26134
26135#[gpui::test]
26136async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
26137    init_test(cx, |_| {});
26138
26139    let language = Arc::new(Language::new(
26140        LanguageConfig::default(),
26141        Some(tree_sitter_rust::LANGUAGE.into()),
26142    ));
26143
26144    // Test hierarchical sibling navigation
26145    let text = r#"
26146        fn outer() {
26147            if condition {
26148                let a = 1;
26149            }
26150            let b = 2;
26151        }
26152
26153        fn another() {
26154            let c = 3;
26155        }
26156    "#;
26157
26158    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
26159    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26160    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
26161
26162    // Wait for parsing to complete
26163    editor
26164        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
26165        .await;
26166
26167    editor.update_in(cx, |editor, window, cx| {
26168        // Start by selecting "let a = 1;" inside the if block
26169        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26170            s.select_display_ranges([
26171                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
26172            ]);
26173        });
26174
26175        let initial_selection = editor.selections.display_ranges(cx);
26176        assert_eq!(initial_selection.len(), 1, "Should have one selection");
26177
26178        // Test select next sibling - should move up levels to find the next sibling
26179        // Since "let a = 1;" has no siblings in the if block, it should move up
26180        // to find "let b = 2;" which is a sibling of the if block
26181        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26182        let next_selection = editor.selections.display_ranges(cx);
26183
26184        // Should have a selection and it should be different from the initial
26185        assert_eq!(
26186            next_selection.len(),
26187            1,
26188            "Should have one selection after next"
26189        );
26190        assert_ne!(
26191            next_selection[0], initial_selection[0],
26192            "Next sibling selection should be different"
26193        );
26194
26195        // Test hierarchical navigation by going to the end of the current function
26196        // and trying to navigate to the next function
26197        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26198            s.select_display_ranges([
26199                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
26200            ]);
26201        });
26202
26203        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26204        let function_next_selection = editor.selections.display_ranges(cx);
26205
26206        // Should move to the next function
26207        assert_eq!(
26208            function_next_selection.len(),
26209            1,
26210            "Should have one selection after function next"
26211        );
26212
26213        // Test select previous sibling navigation
26214        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
26215        let prev_selection = editor.selections.display_ranges(cx);
26216
26217        // Should have a selection and it should be different
26218        assert_eq!(
26219            prev_selection.len(),
26220            1,
26221            "Should have one selection after prev"
26222        );
26223        assert_ne!(
26224            prev_selection[0], function_next_selection[0],
26225            "Previous sibling selection should be different from next"
26226        );
26227    });
26228}
26229
26230#[gpui::test]
26231async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
26232    init_test(cx, |_| {});
26233
26234    let mut cx = EditorTestContext::new(cx).await;
26235    cx.set_state(
26236        "let ˇvariable = 42;
26237let another = variable + 1;
26238let result = variable * 2;",
26239    );
26240
26241    // Set up document highlights manually (simulating LSP response)
26242    cx.update_editor(|editor, _window, cx| {
26243        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26244
26245        // Create highlights for "variable" occurrences
26246        let highlight_ranges = [
26247            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26248            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26249            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26250        ];
26251
26252        let anchor_ranges: Vec<_> = highlight_ranges
26253            .iter()
26254            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26255            .collect();
26256
26257        editor.highlight_background::<DocumentHighlightRead>(
26258            &anchor_ranges,
26259            |theme| theme.colors().editor_document_highlight_read_background,
26260            cx,
26261        );
26262    });
26263
26264    // Go to next highlight - should move to second "variable"
26265    cx.update_editor(|editor, window, cx| {
26266        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26267    });
26268    cx.assert_editor_state(
26269        "let variable = 42;
26270let another = ˇvariable + 1;
26271let result = variable * 2;",
26272    );
26273
26274    // Go to next highlight - should move to third "variable"
26275    cx.update_editor(|editor, window, cx| {
26276        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26277    });
26278    cx.assert_editor_state(
26279        "let variable = 42;
26280let another = variable + 1;
26281let result = ˇvariable * 2;",
26282    );
26283
26284    // Go to next highlight - should stay at third "variable" (no wrap-around)
26285    cx.update_editor(|editor, window, cx| {
26286        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26287    });
26288    cx.assert_editor_state(
26289        "let variable = 42;
26290let another = variable + 1;
26291let result = ˇvariable * 2;",
26292    );
26293
26294    // Now test going backwards from third position
26295    cx.update_editor(|editor, window, cx| {
26296        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26297    });
26298    cx.assert_editor_state(
26299        "let variable = 42;
26300let another = ˇvariable + 1;
26301let result = variable * 2;",
26302    );
26303
26304    // Go to previous highlight - should move to first "variable"
26305    cx.update_editor(|editor, window, cx| {
26306        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26307    });
26308    cx.assert_editor_state(
26309        "let ˇvariable = 42;
26310let another = variable + 1;
26311let result = variable * 2;",
26312    );
26313
26314    // Go to previous highlight - should stay on first "variable"
26315    cx.update_editor(|editor, window, cx| {
26316        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26317    });
26318    cx.assert_editor_state(
26319        "let ˇvariable = 42;
26320let another = variable + 1;
26321let result = variable * 2;",
26322    );
26323}
26324
26325#[gpui::test]
26326async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26327    cx: &mut gpui::TestAppContext,
26328) {
26329    init_test(cx, |_| {});
26330
26331    let url = "https://zed.dev";
26332
26333    let markdown_language = Arc::new(Language::new(
26334        LanguageConfig {
26335            name: "Markdown".into(),
26336            ..LanguageConfig::default()
26337        },
26338        None,
26339    ));
26340
26341    let mut cx = EditorTestContext::new(cx).await;
26342    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26343    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26344
26345    cx.update_editor(|editor, window, cx| {
26346        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26347        editor.paste(&Paste, window, cx);
26348    });
26349
26350    cx.assert_editor_state(&format!(
26351        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26352    ));
26353}
26354
26355#[gpui::test]
26356async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26357    cx: &mut gpui::TestAppContext,
26358) {
26359    init_test(cx, |_| {});
26360
26361    let url = "https://zed.dev";
26362
26363    let markdown_language = Arc::new(Language::new(
26364        LanguageConfig {
26365            name: "Markdown".into(),
26366            ..LanguageConfig::default()
26367        },
26368        None,
26369    ));
26370
26371    let mut cx = EditorTestContext::new(cx).await;
26372    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26373    cx.set_state(&format!(
26374        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26375    ));
26376
26377    cx.update_editor(|editor, window, cx| {
26378        editor.copy(&Copy, window, cx);
26379    });
26380
26381    cx.set_state(&format!(
26382        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26383    ));
26384
26385    cx.update_editor(|editor, window, cx| {
26386        editor.paste(&Paste, window, cx);
26387    });
26388
26389    cx.assert_editor_state(&format!(
26390        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26391    ));
26392}
26393
26394#[gpui::test]
26395async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26396    cx: &mut gpui::TestAppContext,
26397) {
26398    init_test(cx, |_| {});
26399
26400    let url = "https://zed.dev";
26401
26402    let markdown_language = Arc::new(Language::new(
26403        LanguageConfig {
26404            name: "Markdown".into(),
26405            ..LanguageConfig::default()
26406        },
26407        None,
26408    ));
26409
26410    let mut cx = EditorTestContext::new(cx).await;
26411    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26412    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26413
26414    cx.update_editor(|editor, window, cx| {
26415        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26416        editor.paste(&Paste, window, cx);
26417    });
26418
26419    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26420}
26421
26422#[gpui::test]
26423async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26424    cx: &mut gpui::TestAppContext,
26425) {
26426    init_test(cx, |_| {});
26427
26428    let text = "Awesome";
26429
26430    let markdown_language = Arc::new(Language::new(
26431        LanguageConfig {
26432            name: "Markdown".into(),
26433            ..LanguageConfig::default()
26434        },
26435        None,
26436    ));
26437
26438    let mut cx = EditorTestContext::new(cx).await;
26439    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26440    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26441
26442    cx.update_editor(|editor, window, cx| {
26443        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26444        editor.paste(&Paste, window, cx);
26445    });
26446
26447    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26448}
26449
26450#[gpui::test]
26451async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26452    cx: &mut gpui::TestAppContext,
26453) {
26454    init_test(cx, |_| {});
26455
26456    let url = "https://zed.dev";
26457
26458    let markdown_language = Arc::new(Language::new(
26459        LanguageConfig {
26460            name: "Rust".into(),
26461            ..LanguageConfig::default()
26462        },
26463        None,
26464    ));
26465
26466    let mut cx = EditorTestContext::new(cx).await;
26467    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26468    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26469
26470    cx.update_editor(|editor, window, cx| {
26471        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26472        editor.paste(&Paste, window, cx);
26473    });
26474
26475    cx.assert_editor_state(&format!(
26476        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26477    ));
26478}
26479
26480#[gpui::test]
26481async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26482    cx: &mut TestAppContext,
26483) {
26484    init_test(cx, |_| {});
26485
26486    let url = "https://zed.dev";
26487
26488    let markdown_language = Arc::new(Language::new(
26489        LanguageConfig {
26490            name: "Markdown".into(),
26491            ..LanguageConfig::default()
26492        },
26493        None,
26494    ));
26495
26496    let (editor, cx) = cx.add_window_view(|window, cx| {
26497        let multi_buffer = MultiBuffer::build_multi(
26498            [
26499                ("this will embed -> link", vec![Point::row_range(0..1)]),
26500                ("this will replace -> link", vec![Point::row_range(0..1)]),
26501            ],
26502            cx,
26503        );
26504        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26505        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26506            s.select_ranges(vec![
26507                Point::new(0, 19)..Point::new(0, 23),
26508                Point::new(1, 21)..Point::new(1, 25),
26509            ])
26510        });
26511        let first_buffer_id = multi_buffer
26512            .read(cx)
26513            .excerpt_buffer_ids()
26514            .into_iter()
26515            .next()
26516            .unwrap();
26517        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26518        first_buffer.update(cx, |buffer, cx| {
26519            buffer.set_language(Some(markdown_language.clone()), cx);
26520        });
26521
26522        editor
26523    });
26524    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26525
26526    cx.update_editor(|editor, window, cx| {
26527        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26528        editor.paste(&Paste, window, cx);
26529    });
26530
26531    cx.assert_editor_state(&format!(
26532        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26533    ));
26534}
26535
26536#[gpui::test]
26537async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
26538    init_test(cx, |_| {});
26539
26540    let fs = FakeFs::new(cx.executor());
26541    fs.insert_tree(
26542        path!("/project"),
26543        json!({
26544            "first.rs": "# First Document\nSome content here.",
26545            "second.rs": "Plain text content for second file.",
26546        }),
26547    )
26548    .await;
26549
26550    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
26551    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26552    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26553
26554    let language = rust_lang();
26555    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26556    language_registry.add(language.clone());
26557    let mut fake_servers = language_registry.register_fake_lsp(
26558        "Rust",
26559        FakeLspAdapter {
26560            ..FakeLspAdapter::default()
26561        },
26562    );
26563
26564    let buffer1 = project
26565        .update(cx, |project, cx| {
26566            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
26567        })
26568        .await
26569        .unwrap();
26570    let buffer2 = project
26571        .update(cx, |project, cx| {
26572            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
26573        })
26574        .await
26575        .unwrap();
26576
26577    let multi_buffer = cx.new(|cx| {
26578        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
26579        multi_buffer.set_excerpts_for_path(
26580            PathKey::for_buffer(&buffer1, cx),
26581            buffer1.clone(),
26582            [Point::zero()..buffer1.read(cx).max_point()],
26583            3,
26584            cx,
26585        );
26586        multi_buffer.set_excerpts_for_path(
26587            PathKey::for_buffer(&buffer2, cx),
26588            buffer2.clone(),
26589            [Point::zero()..buffer1.read(cx).max_point()],
26590            3,
26591            cx,
26592        );
26593        multi_buffer
26594    });
26595
26596    let (editor, cx) = cx.add_window_view(|window, cx| {
26597        Editor::new(
26598            EditorMode::full(),
26599            multi_buffer,
26600            Some(project.clone()),
26601            window,
26602            cx,
26603        )
26604    });
26605
26606    let fake_language_server = fake_servers.next().await.unwrap();
26607
26608    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
26609
26610    let save = editor.update_in(cx, |editor, window, cx| {
26611        assert!(editor.is_dirty(cx));
26612
26613        editor.save(
26614            SaveOptions {
26615                format: true,
26616                autosave: true,
26617            },
26618            project,
26619            window,
26620            cx,
26621        )
26622    });
26623    let (start_edit_tx, start_edit_rx) = oneshot::channel();
26624    let (done_edit_tx, done_edit_rx) = oneshot::channel();
26625    let mut done_edit_rx = Some(done_edit_rx);
26626    let mut start_edit_tx = Some(start_edit_tx);
26627
26628    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
26629        start_edit_tx.take().unwrap().send(()).unwrap();
26630        let done_edit_rx = done_edit_rx.take().unwrap();
26631        async move {
26632            done_edit_rx.await.unwrap();
26633            Ok(None)
26634        }
26635    });
26636
26637    start_edit_rx.await.unwrap();
26638    buffer2
26639        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
26640        .unwrap();
26641
26642    done_edit_tx.send(()).unwrap();
26643
26644    save.await.unwrap();
26645    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
26646}
26647
26648#[track_caller]
26649fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26650    editor
26651        .all_inlays(cx)
26652        .into_iter()
26653        .filter_map(|inlay| inlay.get_color())
26654        .map(Rgba::from)
26655        .collect()
26656}
26657
26658#[gpui::test]
26659fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
26660    init_test(cx, |_| {});
26661
26662    let editor = cx.add_window(|window, cx| {
26663        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
26664        build_editor(buffer, window, cx)
26665    });
26666
26667    editor
26668        .update(cx, |editor, window, cx| {
26669            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26670                s.select_display_ranges([
26671                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
26672                ])
26673            });
26674
26675            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
26676
26677            assert_eq!(
26678                editor.display_text(cx),
26679                "line1\nline2\nline2",
26680                "Duplicating last line upward should create duplicate above, not on same line"
26681            );
26682
26683            assert_eq!(
26684                editor.selections.display_ranges(cx),
26685                vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)],
26686                "Selection should remain on the original line"
26687            );
26688        })
26689        .unwrap();
26690}
26691
26692#[gpui::test]
26693async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
26694    init_test(cx, |_| {});
26695
26696    let mut cx = EditorTestContext::new(cx).await;
26697
26698    cx.set_state("line1\nline2ˇ");
26699
26700    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
26701
26702    let clipboard_text = cx
26703        .read_from_clipboard()
26704        .and_then(|item| item.text().as_deref().map(str::to_string));
26705
26706    assert_eq!(
26707        clipboard_text,
26708        Some("line2\n".to_string()),
26709        "Copying a line without trailing newline should include a newline"
26710    );
26711
26712    cx.set_state("line1\nˇ");
26713
26714    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
26715
26716    cx.assert_editor_state("line1\nline2\nˇ");
26717}