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_clone(cx: &mut TestAppContext) {
  623    init_test(cx, |_| {});
  624
  625    let (text, selection_ranges) = marked_text_ranges(
  626        indoc! {"
  627            one
  628            two
  629            threeˇ
  630            four
  631            fiveˇ
  632        "},
  633        true,
  634    );
  635
  636    let editor = cx.add_window(|window, cx| {
  637        let buffer = MultiBuffer::build_simple(&text, cx);
  638        build_editor(buffer, window, cx)
  639    });
  640
  641    _ = editor.update(cx, |editor, window, cx| {
  642        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  643            s.select_ranges(selection_ranges.clone())
  644        });
  645        editor.fold_creases(
  646            vec![
  647                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  648                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  649            ],
  650            true,
  651            window,
  652            cx,
  653        );
  654    });
  655
  656    let cloned_editor = editor
  657        .update(cx, |editor, _, cx| {
  658            cx.open_window(Default::default(), |window, cx| {
  659                cx.new(|cx| editor.clone(window, cx))
  660            })
  661        })
  662        .unwrap()
  663        .unwrap();
  664
  665    let snapshot = editor
  666        .update(cx, |e, window, cx| e.snapshot(window, cx))
  667        .unwrap();
  668    let cloned_snapshot = cloned_editor
  669        .update(cx, |e, window, cx| e.snapshot(window, cx))
  670        .unwrap();
  671
  672    assert_eq!(
  673        cloned_editor
  674            .update(cx, |e, _, cx| e.display_text(cx))
  675            .unwrap(),
  676        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  677    );
  678    assert_eq!(
  679        cloned_snapshot
  680            .folds_in_range(0..text.len())
  681            .collect::<Vec<_>>(),
  682        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  683    );
  684    assert_set_eq!(
  685        cloned_editor
  686            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  687            .unwrap(),
  688        editor
  689            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  690            .unwrap()
  691    );
  692    assert_set_eq!(
  693        cloned_editor
  694            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  695            .unwrap(),
  696        editor
  697            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  698            .unwrap()
  699    );
  700}
  701
  702#[gpui::test]
  703async fn test_navigation_history(cx: &mut TestAppContext) {
  704    init_test(cx, |_| {});
  705
  706    use workspace::item::Item;
  707
  708    let fs = FakeFs::new(cx.executor());
  709    let project = Project::test(fs, [], cx).await;
  710    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  711    let pane = workspace
  712        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  713        .unwrap();
  714
  715    _ = workspace.update(cx, |_v, window, cx| {
  716        cx.new(|cx| {
  717            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  718            let mut editor = build_editor(buffer, window, cx);
  719            let handle = cx.entity();
  720            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  721
  722            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  723                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  724            }
  725
  726            // Move the cursor a small distance.
  727            // Nothing is added to the navigation history.
  728            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  729                s.select_display_ranges([
  730                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  731                ])
  732            });
  733            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  734                s.select_display_ranges([
  735                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  736                ])
  737            });
  738            assert!(pop_history(&mut editor, cx).is_none());
  739
  740            // Move the cursor a large distance.
  741            // The history can jump back to the previous position.
  742            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  743                s.select_display_ranges([
  744                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  745                ])
  746            });
  747            let nav_entry = pop_history(&mut editor, cx).unwrap();
  748            editor.navigate(nav_entry.data.unwrap(), window, cx);
  749            assert_eq!(nav_entry.item.id(), cx.entity_id());
  750            assert_eq!(
  751                editor.selections.display_ranges(cx),
  752                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  753            );
  754            assert!(pop_history(&mut editor, cx).is_none());
  755
  756            // Move the cursor a small distance via the mouse.
  757            // Nothing is added to the navigation history.
  758            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  759            editor.end_selection(window, cx);
  760            assert_eq!(
  761                editor.selections.display_ranges(cx),
  762                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  763            );
  764            assert!(pop_history(&mut editor, cx).is_none());
  765
  766            // Move the cursor a large distance via the mouse.
  767            // The history can jump back to the previous position.
  768            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  769            editor.end_selection(window, cx);
  770            assert_eq!(
  771                editor.selections.display_ranges(cx),
  772                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  773            );
  774            let nav_entry = pop_history(&mut editor, cx).unwrap();
  775            editor.navigate(nav_entry.data.unwrap(), window, cx);
  776            assert_eq!(nav_entry.item.id(), cx.entity_id());
  777            assert_eq!(
  778                editor.selections.display_ranges(cx),
  779                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  780            );
  781            assert!(pop_history(&mut editor, cx).is_none());
  782
  783            // Set scroll position to check later
  784            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  785            let original_scroll_position = editor.scroll_manager.anchor();
  786
  787            // Jump to the end of the document and adjust scroll
  788            editor.move_to_end(&MoveToEnd, window, cx);
  789            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  790            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  791
  792            let nav_entry = pop_history(&mut editor, cx).unwrap();
  793            editor.navigate(nav_entry.data.unwrap(), window, cx);
  794            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  795
  796            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  797            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  798            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  799            let invalid_point = Point::new(9999, 0);
  800            editor.navigate(
  801                Box::new(NavigationData {
  802                    cursor_anchor: invalid_anchor,
  803                    cursor_position: invalid_point,
  804                    scroll_anchor: ScrollAnchor {
  805                        anchor: invalid_anchor,
  806                        offset: Default::default(),
  807                    },
  808                    scroll_top_row: invalid_point.row,
  809                }),
  810                window,
  811                cx,
  812            );
  813            assert_eq!(
  814                editor.selections.display_ranges(cx),
  815                &[editor.max_point(cx)..editor.max_point(cx)]
  816            );
  817            assert_eq!(
  818                editor.scroll_position(cx),
  819                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  820            );
  821
  822            editor
  823        })
  824    });
  825}
  826
  827#[gpui::test]
  828fn test_cancel(cx: &mut TestAppContext) {
  829    init_test(cx, |_| {});
  830
  831    let editor = cx.add_window(|window, cx| {
  832        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  833        build_editor(buffer, window, cx)
  834    });
  835
  836    _ = editor.update(cx, |editor, window, cx| {
  837        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  838        editor.update_selection(
  839            DisplayPoint::new(DisplayRow(1), 1),
  840            0,
  841            gpui::Point::<f32>::default(),
  842            window,
  843            cx,
  844        );
  845        editor.end_selection(window, cx);
  846
  847        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  848        editor.update_selection(
  849            DisplayPoint::new(DisplayRow(0), 3),
  850            0,
  851            gpui::Point::<f32>::default(),
  852            window,
  853            cx,
  854        );
  855        editor.end_selection(window, cx);
  856        assert_eq!(
  857            editor.selections.display_ranges(cx),
  858            [
  859                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  860                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  861            ]
  862        );
  863    });
  864
  865    _ = editor.update(cx, |editor, window, cx| {
  866        editor.cancel(&Cancel, window, cx);
  867        assert_eq!(
  868            editor.selections.display_ranges(cx),
  869            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  870        );
  871    });
  872
  873    _ = editor.update(cx, |editor, window, cx| {
  874        editor.cancel(&Cancel, window, cx);
  875        assert_eq!(
  876            editor.selections.display_ranges(cx),
  877            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  878        );
  879    });
  880}
  881
  882#[gpui::test]
  883fn test_fold_action(cx: &mut TestAppContext) {
  884    init_test(cx, |_| {});
  885
  886    let editor = cx.add_window(|window, cx| {
  887        let buffer = MultiBuffer::build_simple(
  888            &"
  889                impl Foo {
  890                    // Hello!
  891
  892                    fn a() {
  893                        1
  894                    }
  895
  896                    fn b() {
  897                        2
  898                    }
  899
  900                    fn c() {
  901                        3
  902                    }
  903                }
  904            "
  905            .unindent(),
  906            cx,
  907        );
  908        build_editor(buffer, window, cx)
  909    });
  910
  911    _ = editor.update(cx, |editor, window, cx| {
  912        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  913            s.select_display_ranges([
  914                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  915            ]);
  916        });
  917        editor.fold(&Fold, window, cx);
  918        assert_eq!(
  919            editor.display_text(cx),
  920            "
  921                impl Foo {
  922                    // Hello!
  923
  924                    fn a() {
  925                        1
  926                    }
  927
  928                    fn b() {⋯
  929                    }
  930
  931                    fn c() {⋯
  932                    }
  933                }
  934            "
  935            .unindent(),
  936        );
  937
  938        editor.fold(&Fold, window, cx);
  939        assert_eq!(
  940            editor.display_text(cx),
  941            "
  942                impl Foo {⋯
  943                }
  944            "
  945            .unindent(),
  946        );
  947
  948        editor.unfold_lines(&UnfoldLines, window, cx);
  949        assert_eq!(
  950            editor.display_text(cx),
  951            "
  952                impl Foo {
  953                    // Hello!
  954
  955                    fn a() {
  956                        1
  957                    }
  958
  959                    fn b() {⋯
  960                    }
  961
  962                    fn c() {⋯
  963                    }
  964                }
  965            "
  966            .unindent(),
  967        );
  968
  969        editor.unfold_lines(&UnfoldLines, window, cx);
  970        assert_eq!(
  971            editor.display_text(cx),
  972            editor.buffer.read(cx).read(cx).text()
  973        );
  974    });
  975}
  976
  977#[gpui::test]
  978fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  979    init_test(cx, |_| {});
  980
  981    let editor = cx.add_window(|window, cx| {
  982        let buffer = MultiBuffer::build_simple(
  983            &"
  984                class Foo:
  985                    # Hello!
  986
  987                    def a():
  988                        print(1)
  989
  990                    def b():
  991                        print(2)
  992
  993                    def c():
  994                        print(3)
  995            "
  996            .unindent(),
  997            cx,
  998        );
  999        build_editor(buffer, window, cx)
 1000    });
 1001
 1002    _ = editor.update(cx, |editor, window, cx| {
 1003        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1004            s.select_display_ranges([
 1005                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1006            ]);
 1007        });
 1008        editor.fold(&Fold, window, cx);
 1009        assert_eq!(
 1010            editor.display_text(cx),
 1011            "
 1012                class Foo:
 1013                    # Hello!
 1014
 1015                    def a():
 1016                        print(1)
 1017
 1018                    def b():⋯
 1019
 1020                    def c():⋯
 1021            "
 1022            .unindent(),
 1023        );
 1024
 1025        editor.fold(&Fold, window, cx);
 1026        assert_eq!(
 1027            editor.display_text(cx),
 1028            "
 1029                class Foo:⋯
 1030            "
 1031            .unindent(),
 1032        );
 1033
 1034        editor.unfold_lines(&UnfoldLines, window, cx);
 1035        assert_eq!(
 1036            editor.display_text(cx),
 1037            "
 1038                class Foo:
 1039                    # Hello!
 1040
 1041                    def a():
 1042                        print(1)
 1043
 1044                    def b():⋯
 1045
 1046                    def c():⋯
 1047            "
 1048            .unindent(),
 1049        );
 1050
 1051        editor.unfold_lines(&UnfoldLines, window, cx);
 1052        assert_eq!(
 1053            editor.display_text(cx),
 1054            editor.buffer.read(cx).read(cx).text()
 1055        );
 1056    });
 1057}
 1058
 1059#[gpui::test]
 1060fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1061    init_test(cx, |_| {});
 1062
 1063    let editor = cx.add_window(|window, cx| {
 1064        let buffer = MultiBuffer::build_simple(
 1065            &"
 1066                class Foo:
 1067                    # Hello!
 1068
 1069                    def a():
 1070                        print(1)
 1071
 1072                    def b():
 1073                        print(2)
 1074
 1075
 1076                    def c():
 1077                        print(3)
 1078
 1079
 1080            "
 1081            .unindent(),
 1082            cx,
 1083        );
 1084        build_editor(buffer, window, cx)
 1085    });
 1086
 1087    _ = editor.update(cx, |editor, window, cx| {
 1088        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1089            s.select_display_ranges([
 1090                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1091            ]);
 1092        });
 1093        editor.fold(&Fold, window, cx);
 1094        assert_eq!(
 1095            editor.display_text(cx),
 1096            "
 1097                class Foo:
 1098                    # Hello!
 1099
 1100                    def a():
 1101                        print(1)
 1102
 1103                    def b():⋯
 1104
 1105
 1106                    def c():⋯
 1107
 1108
 1109            "
 1110            .unindent(),
 1111        );
 1112
 1113        editor.fold(&Fold, window, cx);
 1114        assert_eq!(
 1115            editor.display_text(cx),
 1116            "
 1117                class Foo:⋯
 1118
 1119
 1120            "
 1121            .unindent(),
 1122        );
 1123
 1124        editor.unfold_lines(&UnfoldLines, window, cx);
 1125        assert_eq!(
 1126            editor.display_text(cx),
 1127            "
 1128                class Foo:
 1129                    # Hello!
 1130
 1131                    def a():
 1132                        print(1)
 1133
 1134                    def b():⋯
 1135
 1136
 1137                    def c():⋯
 1138
 1139
 1140            "
 1141            .unindent(),
 1142        );
 1143
 1144        editor.unfold_lines(&UnfoldLines, window, cx);
 1145        assert_eq!(
 1146            editor.display_text(cx),
 1147            editor.buffer.read(cx).read(cx).text()
 1148        );
 1149    });
 1150}
 1151
 1152#[gpui::test]
 1153fn test_fold_at_level(cx: &mut TestAppContext) {
 1154    init_test(cx, |_| {});
 1155
 1156    let editor = cx.add_window(|window, cx| {
 1157        let buffer = MultiBuffer::build_simple(
 1158            &"
 1159                class Foo:
 1160                    # Hello!
 1161
 1162                    def a():
 1163                        print(1)
 1164
 1165                    def b():
 1166                        print(2)
 1167
 1168
 1169                class Bar:
 1170                    # World!
 1171
 1172                    def a():
 1173                        print(1)
 1174
 1175                    def b():
 1176                        print(2)
 1177
 1178
 1179            "
 1180            .unindent(),
 1181            cx,
 1182        );
 1183        build_editor(buffer, window, cx)
 1184    });
 1185
 1186    _ = editor.update(cx, |editor, window, cx| {
 1187        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1188        assert_eq!(
 1189            editor.display_text(cx),
 1190            "
 1191                class Foo:
 1192                    # Hello!
 1193
 1194                    def a():⋯
 1195
 1196                    def b():⋯
 1197
 1198
 1199                class Bar:
 1200                    # World!
 1201
 1202                    def a():⋯
 1203
 1204                    def b():⋯
 1205
 1206
 1207            "
 1208            .unindent(),
 1209        );
 1210
 1211        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1212        assert_eq!(
 1213            editor.display_text(cx),
 1214            "
 1215                class Foo:⋯
 1216
 1217
 1218                class Bar:⋯
 1219
 1220
 1221            "
 1222            .unindent(),
 1223        );
 1224
 1225        editor.unfold_all(&UnfoldAll, window, cx);
 1226        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1227        assert_eq!(
 1228            editor.display_text(cx),
 1229            "
 1230                class Foo:
 1231                    # Hello!
 1232
 1233                    def a():
 1234                        print(1)
 1235
 1236                    def b():
 1237                        print(2)
 1238
 1239
 1240                class Bar:
 1241                    # World!
 1242
 1243                    def a():
 1244                        print(1)
 1245
 1246                    def b():
 1247                        print(2)
 1248
 1249
 1250            "
 1251            .unindent(),
 1252        );
 1253
 1254        assert_eq!(
 1255            editor.display_text(cx),
 1256            editor.buffer.read(cx).read(cx).text()
 1257        );
 1258        let (_, positions) = marked_text_ranges(
 1259            &"
 1260                       class Foo:
 1261                           # Hello!
 1262
 1263                           def a():
 1264                              print(1)
 1265
 1266                           def b():
 1267                               p«riˇ»nt(2)
 1268
 1269
 1270                       class Bar:
 1271                           # World!
 1272
 1273                           def a():
 1274                               «ˇprint(1)
 1275
 1276                           def b():
 1277                               print(2)»
 1278
 1279
 1280                   "
 1281            .unindent(),
 1282            true,
 1283        );
 1284
 1285        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1286            s.select_ranges(positions)
 1287        });
 1288
 1289        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1290        assert_eq!(
 1291            editor.display_text(cx),
 1292            "
 1293                class Foo:
 1294                    # Hello!
 1295
 1296                    def a():⋯
 1297
 1298                    def b():
 1299                        print(2)
 1300
 1301
 1302                class Bar:
 1303                    # World!
 1304
 1305                    def a():
 1306                        print(1)
 1307
 1308                    def b():
 1309                        print(2)
 1310
 1311
 1312            "
 1313            .unindent(),
 1314        );
 1315    });
 1316}
 1317
 1318#[gpui::test]
 1319fn test_move_cursor(cx: &mut TestAppContext) {
 1320    init_test(cx, |_| {});
 1321
 1322    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1323    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1324
 1325    buffer.update(cx, |buffer, cx| {
 1326        buffer.edit(
 1327            vec![
 1328                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1329                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1330            ],
 1331            None,
 1332            cx,
 1333        );
 1334    });
 1335    _ = editor.update(cx, |editor, window, cx| {
 1336        assert_eq!(
 1337            editor.selections.display_ranges(cx),
 1338            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1339        );
 1340
 1341        editor.move_down(&MoveDown, window, cx);
 1342        assert_eq!(
 1343            editor.selections.display_ranges(cx),
 1344            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1345        );
 1346
 1347        editor.move_right(&MoveRight, window, cx);
 1348        assert_eq!(
 1349            editor.selections.display_ranges(cx),
 1350            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1351        );
 1352
 1353        editor.move_left(&MoveLeft, window, cx);
 1354        assert_eq!(
 1355            editor.selections.display_ranges(cx),
 1356            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1357        );
 1358
 1359        editor.move_up(&MoveUp, window, cx);
 1360        assert_eq!(
 1361            editor.selections.display_ranges(cx),
 1362            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1363        );
 1364
 1365        editor.move_to_end(&MoveToEnd, window, cx);
 1366        assert_eq!(
 1367            editor.selections.display_ranges(cx),
 1368            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1369        );
 1370
 1371        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1372        assert_eq!(
 1373            editor.selections.display_ranges(cx),
 1374            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1375        );
 1376
 1377        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1378            s.select_display_ranges([
 1379                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1380            ]);
 1381        });
 1382        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1383        assert_eq!(
 1384            editor.selections.display_ranges(cx),
 1385            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1386        );
 1387
 1388        editor.select_to_end(&SelectToEnd, window, cx);
 1389        assert_eq!(
 1390            editor.selections.display_ranges(cx),
 1391            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1392        );
 1393    });
 1394}
 1395
 1396#[gpui::test]
 1397fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1398    init_test(cx, |_| {});
 1399
 1400    let editor = cx.add_window(|window, cx| {
 1401        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1402        build_editor(buffer, window, cx)
 1403    });
 1404
 1405    assert_eq!('🟥'.len_utf8(), 4);
 1406    assert_eq!('α'.len_utf8(), 2);
 1407
 1408    _ = editor.update(cx, |editor, window, cx| {
 1409        editor.fold_creases(
 1410            vec![
 1411                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1412                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1413                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1414            ],
 1415            true,
 1416            window,
 1417            cx,
 1418        );
 1419        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1420
 1421        editor.move_right(&MoveRight, window, cx);
 1422        assert_eq!(
 1423            editor.selections.display_ranges(cx),
 1424            &[empty_range(0, "🟥".len())]
 1425        );
 1426        editor.move_right(&MoveRight, window, cx);
 1427        assert_eq!(
 1428            editor.selections.display_ranges(cx),
 1429            &[empty_range(0, "🟥🟧".len())]
 1430        );
 1431        editor.move_right(&MoveRight, window, cx);
 1432        assert_eq!(
 1433            editor.selections.display_ranges(cx),
 1434            &[empty_range(0, "🟥🟧⋯".len())]
 1435        );
 1436
 1437        editor.move_down(&MoveDown, window, cx);
 1438        assert_eq!(
 1439            editor.selections.display_ranges(cx),
 1440            &[empty_range(1, "ab⋯e".len())]
 1441        );
 1442        editor.move_left(&MoveLeft, window, cx);
 1443        assert_eq!(
 1444            editor.selections.display_ranges(cx),
 1445            &[empty_range(1, "ab⋯".len())]
 1446        );
 1447        editor.move_left(&MoveLeft, window, cx);
 1448        assert_eq!(
 1449            editor.selections.display_ranges(cx),
 1450            &[empty_range(1, "ab".len())]
 1451        );
 1452        editor.move_left(&MoveLeft, window, cx);
 1453        assert_eq!(
 1454            editor.selections.display_ranges(cx),
 1455            &[empty_range(1, "a".len())]
 1456        );
 1457
 1458        editor.move_down(&MoveDown, window, cx);
 1459        assert_eq!(
 1460            editor.selections.display_ranges(cx),
 1461            &[empty_range(2, "α".len())]
 1462        );
 1463        editor.move_right(&MoveRight, window, cx);
 1464        assert_eq!(
 1465            editor.selections.display_ranges(cx),
 1466            &[empty_range(2, "αβ".len())]
 1467        );
 1468        editor.move_right(&MoveRight, window, cx);
 1469        assert_eq!(
 1470            editor.selections.display_ranges(cx),
 1471            &[empty_range(2, "αβ⋯".len())]
 1472        );
 1473        editor.move_right(&MoveRight, window, cx);
 1474        assert_eq!(
 1475            editor.selections.display_ranges(cx),
 1476            &[empty_range(2, "αβ⋯ε".len())]
 1477        );
 1478
 1479        editor.move_up(&MoveUp, window, cx);
 1480        assert_eq!(
 1481            editor.selections.display_ranges(cx),
 1482            &[empty_range(1, "ab⋯e".len())]
 1483        );
 1484        editor.move_down(&MoveDown, window, cx);
 1485        assert_eq!(
 1486            editor.selections.display_ranges(cx),
 1487            &[empty_range(2, "αβ⋯ε".len())]
 1488        );
 1489        editor.move_up(&MoveUp, window, cx);
 1490        assert_eq!(
 1491            editor.selections.display_ranges(cx),
 1492            &[empty_range(1, "ab⋯e".len())]
 1493        );
 1494
 1495        editor.move_up(&MoveUp, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[empty_range(0, "🟥🟧".len())]
 1499        );
 1500        editor.move_left(&MoveLeft, window, cx);
 1501        assert_eq!(
 1502            editor.selections.display_ranges(cx),
 1503            &[empty_range(0, "🟥".len())]
 1504        );
 1505        editor.move_left(&MoveLeft, window, cx);
 1506        assert_eq!(
 1507            editor.selections.display_ranges(cx),
 1508            &[empty_range(0, "".len())]
 1509        );
 1510    });
 1511}
 1512
 1513#[gpui::test]
 1514fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1515    init_test(cx, |_| {});
 1516
 1517    let editor = cx.add_window(|window, cx| {
 1518        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1519        build_editor(buffer, window, cx)
 1520    });
 1521    _ = editor.update(cx, |editor, window, cx| {
 1522        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1523            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1524        });
 1525
 1526        // moving above start of document should move selection to start of document,
 1527        // but the next move down should still be at the original goal_x
 1528        editor.move_up(&MoveUp, window, cx);
 1529        assert_eq!(
 1530            editor.selections.display_ranges(cx),
 1531            &[empty_range(0, "".len())]
 1532        );
 1533
 1534        editor.move_down(&MoveDown, window, cx);
 1535        assert_eq!(
 1536            editor.selections.display_ranges(cx),
 1537            &[empty_range(1, "abcd".len())]
 1538        );
 1539
 1540        editor.move_down(&MoveDown, window, cx);
 1541        assert_eq!(
 1542            editor.selections.display_ranges(cx),
 1543            &[empty_range(2, "αβγ".len())]
 1544        );
 1545
 1546        editor.move_down(&MoveDown, window, cx);
 1547        assert_eq!(
 1548            editor.selections.display_ranges(cx),
 1549            &[empty_range(3, "abcd".len())]
 1550        );
 1551
 1552        editor.move_down(&MoveDown, window, cx);
 1553        assert_eq!(
 1554            editor.selections.display_ranges(cx),
 1555            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1556        );
 1557
 1558        // moving past end of document should not change goal_x
 1559        editor.move_down(&MoveDown, window, cx);
 1560        assert_eq!(
 1561            editor.selections.display_ranges(cx),
 1562            &[empty_range(5, "".len())]
 1563        );
 1564
 1565        editor.move_down(&MoveDown, window, cx);
 1566        assert_eq!(
 1567            editor.selections.display_ranges(cx),
 1568            &[empty_range(5, "".len())]
 1569        );
 1570
 1571        editor.move_up(&MoveUp, window, cx);
 1572        assert_eq!(
 1573            editor.selections.display_ranges(cx),
 1574            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1575        );
 1576
 1577        editor.move_up(&MoveUp, window, cx);
 1578        assert_eq!(
 1579            editor.selections.display_ranges(cx),
 1580            &[empty_range(3, "abcd".len())]
 1581        );
 1582
 1583        editor.move_up(&MoveUp, window, cx);
 1584        assert_eq!(
 1585            editor.selections.display_ranges(cx),
 1586            &[empty_range(2, "αβγ".len())]
 1587        );
 1588    });
 1589}
 1590
 1591#[gpui::test]
 1592fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1593    init_test(cx, |_| {});
 1594    let move_to_beg = MoveToBeginningOfLine {
 1595        stop_at_soft_wraps: true,
 1596        stop_at_indent: true,
 1597    };
 1598
 1599    let delete_to_beg = DeleteToBeginningOfLine {
 1600        stop_at_indent: false,
 1601    };
 1602
 1603    let move_to_end = MoveToEndOfLine {
 1604        stop_at_soft_wraps: true,
 1605    };
 1606
 1607    let editor = cx.add_window(|window, cx| {
 1608        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1609        build_editor(buffer, window, cx)
 1610    });
 1611    _ = editor.update(cx, |editor, window, cx| {
 1612        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1613            s.select_display_ranges([
 1614                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1615                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1616            ]);
 1617        });
 1618    });
 1619
 1620    _ = editor.update(cx, |editor, window, cx| {
 1621        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1622        assert_eq!(
 1623            editor.selections.display_ranges(cx),
 1624            &[
 1625                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1626                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1627            ]
 1628        );
 1629    });
 1630
 1631    _ = editor.update(cx, |editor, window, cx| {
 1632        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1633        assert_eq!(
 1634            editor.selections.display_ranges(cx),
 1635            &[
 1636                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1637                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1638            ]
 1639        );
 1640    });
 1641
 1642    _ = editor.update(cx, |editor, window, cx| {
 1643        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1644        assert_eq!(
 1645            editor.selections.display_ranges(cx),
 1646            &[
 1647                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1648                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1649            ]
 1650        );
 1651    });
 1652
 1653    _ = editor.update(cx, |editor, window, cx| {
 1654        editor.move_to_end_of_line(&move_to_end, window, cx);
 1655        assert_eq!(
 1656            editor.selections.display_ranges(cx),
 1657            &[
 1658                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1659                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1660            ]
 1661        );
 1662    });
 1663
 1664    // Moving to the end of line again is a no-op.
 1665    _ = editor.update(cx, |editor, window, cx| {
 1666        editor.move_to_end_of_line(&move_to_end, window, cx);
 1667        assert_eq!(
 1668            editor.selections.display_ranges(cx),
 1669            &[
 1670                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1671                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1672            ]
 1673        );
 1674    });
 1675
 1676    _ = editor.update(cx, |editor, window, cx| {
 1677        editor.move_left(&MoveLeft, window, cx);
 1678        editor.select_to_beginning_of_line(
 1679            &SelectToBeginningOfLine {
 1680                stop_at_soft_wraps: true,
 1681                stop_at_indent: true,
 1682            },
 1683            window,
 1684            cx,
 1685        );
 1686        assert_eq!(
 1687            editor.selections.display_ranges(cx),
 1688            &[
 1689                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1690                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1691            ]
 1692        );
 1693    });
 1694
 1695    _ = editor.update(cx, |editor, window, cx| {
 1696        editor.select_to_beginning_of_line(
 1697            &SelectToBeginningOfLine {
 1698                stop_at_soft_wraps: true,
 1699                stop_at_indent: true,
 1700            },
 1701            window,
 1702            cx,
 1703        );
 1704        assert_eq!(
 1705            editor.selections.display_ranges(cx),
 1706            &[
 1707                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1708                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1709            ]
 1710        );
 1711    });
 1712
 1713    _ = editor.update(cx, |editor, window, cx| {
 1714        editor.select_to_beginning_of_line(
 1715            &SelectToBeginningOfLine {
 1716                stop_at_soft_wraps: true,
 1717                stop_at_indent: true,
 1718            },
 1719            window,
 1720            cx,
 1721        );
 1722        assert_eq!(
 1723            editor.selections.display_ranges(cx),
 1724            &[
 1725                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1726                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1727            ]
 1728        );
 1729    });
 1730
 1731    _ = editor.update(cx, |editor, window, cx| {
 1732        editor.select_to_end_of_line(
 1733            &SelectToEndOfLine {
 1734                stop_at_soft_wraps: true,
 1735            },
 1736            window,
 1737            cx,
 1738        );
 1739        assert_eq!(
 1740            editor.selections.display_ranges(cx),
 1741            &[
 1742                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1743                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1744            ]
 1745        );
 1746    });
 1747
 1748    _ = editor.update(cx, |editor, window, cx| {
 1749        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1750        assert_eq!(editor.display_text(cx), "ab\n  de");
 1751        assert_eq!(
 1752            editor.selections.display_ranges(cx),
 1753            &[
 1754                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1755                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1756            ]
 1757        );
 1758    });
 1759
 1760    _ = editor.update(cx, |editor, window, cx| {
 1761        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1762        assert_eq!(editor.display_text(cx), "\n");
 1763        assert_eq!(
 1764            editor.selections.display_ranges(cx),
 1765            &[
 1766                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1767                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1768            ]
 1769        );
 1770    });
 1771}
 1772
 1773#[gpui::test]
 1774fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1775    init_test(cx, |_| {});
 1776    let move_to_beg = MoveToBeginningOfLine {
 1777        stop_at_soft_wraps: false,
 1778        stop_at_indent: false,
 1779    };
 1780
 1781    let move_to_end = MoveToEndOfLine {
 1782        stop_at_soft_wraps: false,
 1783    };
 1784
 1785    let editor = cx.add_window(|window, cx| {
 1786        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1787        build_editor(buffer, window, cx)
 1788    });
 1789
 1790    _ = editor.update(cx, |editor, window, cx| {
 1791        editor.set_wrap_width(Some(140.0.into()), cx);
 1792
 1793        // We expect the following lines after wrapping
 1794        // ```
 1795        // thequickbrownfox
 1796        // jumpedoverthelazydo
 1797        // gs
 1798        // ```
 1799        // The final `gs` was soft-wrapped onto a new line.
 1800        assert_eq!(
 1801            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1802            editor.display_text(cx),
 1803        );
 1804
 1805        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1806        // Start the cursor at the `k` on the first line
 1807        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1808            s.select_display_ranges([
 1809                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1810            ]);
 1811        });
 1812
 1813        // Moving to the beginning of the line should put us at the beginning of the line.
 1814        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1815        assert_eq!(
 1816            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1817            editor.selections.display_ranges(cx)
 1818        );
 1819
 1820        // Moving to the end of the line should put us at the end of the line.
 1821        editor.move_to_end_of_line(&move_to_end, window, cx);
 1822        assert_eq!(
 1823            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1824            editor.selections.display_ranges(cx)
 1825        );
 1826
 1827        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1828        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1829        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1830            s.select_display_ranges([
 1831                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1832            ]);
 1833        });
 1834
 1835        // Moving to the beginning of the line should put us at the start of the second line of
 1836        // display text, i.e., the `j`.
 1837        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1838        assert_eq!(
 1839            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1840            editor.selections.display_ranges(cx)
 1841        );
 1842
 1843        // Moving to the beginning of the line again should be a no-op.
 1844        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1845        assert_eq!(
 1846            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1847            editor.selections.display_ranges(cx)
 1848        );
 1849
 1850        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1851        // next display line.
 1852        editor.move_to_end_of_line(&move_to_end, window, cx);
 1853        assert_eq!(
 1854            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1855            editor.selections.display_ranges(cx)
 1856        );
 1857
 1858        // Moving to the end of the line again should be a no-op.
 1859        editor.move_to_end_of_line(&move_to_end, window, cx);
 1860        assert_eq!(
 1861            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1862            editor.selections.display_ranges(cx)
 1863        );
 1864    });
 1865}
 1866
 1867#[gpui::test]
 1868fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1869    init_test(cx, |_| {});
 1870
 1871    let move_to_beg = MoveToBeginningOfLine {
 1872        stop_at_soft_wraps: true,
 1873        stop_at_indent: true,
 1874    };
 1875
 1876    let select_to_beg = SelectToBeginningOfLine {
 1877        stop_at_soft_wraps: true,
 1878        stop_at_indent: true,
 1879    };
 1880
 1881    let delete_to_beg = DeleteToBeginningOfLine {
 1882        stop_at_indent: true,
 1883    };
 1884
 1885    let move_to_end = MoveToEndOfLine {
 1886        stop_at_soft_wraps: false,
 1887    };
 1888
 1889    let editor = cx.add_window(|window, cx| {
 1890        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1891        build_editor(buffer, window, cx)
 1892    });
 1893
 1894    _ = editor.update(cx, |editor, window, cx| {
 1895        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1896            s.select_display_ranges([
 1897                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1898                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1899            ]);
 1900        });
 1901
 1902        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1903        // and the second cursor at the first non-whitespace character in the line.
 1904        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1905        assert_eq!(
 1906            editor.selections.display_ranges(cx),
 1907            &[
 1908                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1909                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1910            ]
 1911        );
 1912
 1913        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1914        // and should move the second cursor to the beginning of the line.
 1915        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1916        assert_eq!(
 1917            editor.selections.display_ranges(cx),
 1918            &[
 1919                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1920                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1921            ]
 1922        );
 1923
 1924        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1925        // and should move the second cursor back to the first non-whitespace character in the line.
 1926        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1927        assert_eq!(
 1928            editor.selections.display_ranges(cx),
 1929            &[
 1930                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1931                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1932            ]
 1933        );
 1934
 1935        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1936        // and to the first non-whitespace character in the line for the second cursor.
 1937        editor.move_to_end_of_line(&move_to_end, window, cx);
 1938        editor.move_left(&MoveLeft, window, cx);
 1939        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1940        assert_eq!(
 1941            editor.selections.display_ranges(cx),
 1942            &[
 1943                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1944                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1945            ]
 1946        );
 1947
 1948        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1949        // and should select to the beginning of the line for the second cursor.
 1950        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1951        assert_eq!(
 1952            editor.selections.display_ranges(cx),
 1953            &[
 1954                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1955                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1956            ]
 1957        );
 1958
 1959        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1960        // and should delete to the first non-whitespace character in the line for the second cursor.
 1961        editor.move_to_end_of_line(&move_to_end, window, cx);
 1962        editor.move_left(&MoveLeft, window, cx);
 1963        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1964        assert_eq!(editor.text(cx), "c\n  f");
 1965    });
 1966}
 1967
 1968#[gpui::test]
 1969fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 1970    init_test(cx, |_| {});
 1971
 1972    let move_to_beg = MoveToBeginningOfLine {
 1973        stop_at_soft_wraps: true,
 1974        stop_at_indent: true,
 1975    };
 1976
 1977    let editor = cx.add_window(|window, cx| {
 1978        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 1979        build_editor(buffer, window, cx)
 1980    });
 1981
 1982    _ = editor.update(cx, |editor, window, cx| {
 1983        // test cursor between line_start and indent_start
 1984        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1985            s.select_display_ranges([
 1986                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 1987            ]);
 1988        });
 1989
 1990        // cursor should move to line_start
 1991        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1992        assert_eq!(
 1993            editor.selections.display_ranges(cx),
 1994            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1995        );
 1996
 1997        // cursor should move to indent_start
 1998        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1999        assert_eq!(
 2000            editor.selections.display_ranges(cx),
 2001            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2002        );
 2003
 2004        // cursor should move to back to line_start
 2005        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2006        assert_eq!(
 2007            editor.selections.display_ranges(cx),
 2008            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2009        );
 2010    });
 2011}
 2012
 2013#[gpui::test]
 2014fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2015    init_test(cx, |_| {});
 2016
 2017    let editor = cx.add_window(|window, cx| {
 2018        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2019        build_editor(buffer, window, cx)
 2020    });
 2021    _ = editor.update(cx, |editor, window, cx| {
 2022        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2023            s.select_display_ranges([
 2024                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2025                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2026            ])
 2027        });
 2028        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2029        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2030
 2031        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2032        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2033
 2034        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2035        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2036
 2037        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2038        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2039
 2040        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2041        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2042
 2043        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2044        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2045
 2046        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2047        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2048
 2049        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2050        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2051
 2052        editor.move_right(&MoveRight, window, cx);
 2053        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2054        assert_selection_ranges(
 2055            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2056            editor,
 2057            cx,
 2058        );
 2059
 2060        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2061        assert_selection_ranges(
 2062            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2063            editor,
 2064            cx,
 2065        );
 2066
 2067        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2068        assert_selection_ranges(
 2069            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2070            editor,
 2071            cx,
 2072        );
 2073    });
 2074}
 2075
 2076#[gpui::test]
 2077fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2078    init_test(cx, |_| {});
 2079
 2080    let editor = cx.add_window(|window, cx| {
 2081        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2082        build_editor(buffer, window, cx)
 2083    });
 2084
 2085    _ = editor.update(cx, |editor, window, cx| {
 2086        editor.set_wrap_width(Some(140.0.into()), cx);
 2087        assert_eq!(
 2088            editor.display_text(cx),
 2089            "use one::{\n    two::three::\n    four::five\n};"
 2090        );
 2091
 2092        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2093            s.select_display_ranges([
 2094                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2095            ]);
 2096        });
 2097
 2098        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2099        assert_eq!(
 2100            editor.selections.display_ranges(cx),
 2101            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2102        );
 2103
 2104        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2105        assert_eq!(
 2106            editor.selections.display_ranges(cx),
 2107            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2108        );
 2109
 2110        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2111        assert_eq!(
 2112            editor.selections.display_ranges(cx),
 2113            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2114        );
 2115
 2116        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2117        assert_eq!(
 2118            editor.selections.display_ranges(cx),
 2119            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2120        );
 2121
 2122        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2123        assert_eq!(
 2124            editor.selections.display_ranges(cx),
 2125            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2126        );
 2127
 2128        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2129        assert_eq!(
 2130            editor.selections.display_ranges(cx),
 2131            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2132        );
 2133    });
 2134}
 2135
 2136#[gpui::test]
 2137async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2138    init_test(cx, |_| {});
 2139    let mut cx = EditorTestContext::new(cx).await;
 2140
 2141    let line_height = cx.editor(|editor, window, _| {
 2142        editor
 2143            .style()
 2144            .unwrap()
 2145            .text
 2146            .line_height_in_pixels(window.rem_size())
 2147    });
 2148    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2149
 2150    cx.set_state(
 2151        &r#"ˇone
 2152        two
 2153
 2154        three
 2155        fourˇ
 2156        five
 2157
 2158        six"#
 2159            .unindent(),
 2160    );
 2161
 2162    cx.update_editor(|editor, window, cx| {
 2163        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2164    });
 2165    cx.assert_editor_state(
 2166        &r#"one
 2167        two
 2168        ˇ
 2169        three
 2170        four
 2171        five
 2172        ˇ
 2173        six"#
 2174            .unindent(),
 2175    );
 2176
 2177    cx.update_editor(|editor, window, cx| {
 2178        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2179    });
 2180    cx.assert_editor_state(
 2181        &r#"one
 2182        two
 2183
 2184        three
 2185        four
 2186        five
 2187        ˇ
 2188        sixˇ"#
 2189            .unindent(),
 2190    );
 2191
 2192    cx.update_editor(|editor, window, cx| {
 2193        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2194    });
 2195    cx.assert_editor_state(
 2196        &r#"one
 2197        two
 2198
 2199        three
 2200        four
 2201        five
 2202
 2203        sixˇ"#
 2204            .unindent(),
 2205    );
 2206
 2207    cx.update_editor(|editor, window, cx| {
 2208        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2209    });
 2210    cx.assert_editor_state(
 2211        &r#"one
 2212        two
 2213
 2214        three
 2215        four
 2216        five
 2217        ˇ
 2218        six"#
 2219            .unindent(),
 2220    );
 2221
 2222    cx.update_editor(|editor, window, cx| {
 2223        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2224    });
 2225    cx.assert_editor_state(
 2226        &r#"one
 2227        two
 2228        ˇ
 2229        three
 2230        four
 2231        five
 2232
 2233        six"#
 2234            .unindent(),
 2235    );
 2236
 2237    cx.update_editor(|editor, window, cx| {
 2238        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2239    });
 2240    cx.assert_editor_state(
 2241        &r#"ˇone
 2242        two
 2243
 2244        three
 2245        four
 2246        five
 2247
 2248        six"#
 2249            .unindent(),
 2250    );
 2251}
 2252
 2253#[gpui::test]
 2254async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2255    init_test(cx, |_| {});
 2256    let mut cx = EditorTestContext::new(cx).await;
 2257    let line_height = cx.editor(|editor, window, _| {
 2258        editor
 2259            .style()
 2260            .unwrap()
 2261            .text
 2262            .line_height_in_pixels(window.rem_size())
 2263    });
 2264    let window = cx.window;
 2265    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2266
 2267    cx.set_state(
 2268        r#"ˇone
 2269        two
 2270        three
 2271        four
 2272        five
 2273        six
 2274        seven
 2275        eight
 2276        nine
 2277        ten
 2278        "#,
 2279    );
 2280
 2281    cx.update_editor(|editor, window, cx| {
 2282        assert_eq!(
 2283            editor.snapshot(window, cx).scroll_position(),
 2284            gpui::Point::new(0., 0.)
 2285        );
 2286        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2287        assert_eq!(
 2288            editor.snapshot(window, cx).scroll_position(),
 2289            gpui::Point::new(0., 3.)
 2290        );
 2291        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2292        assert_eq!(
 2293            editor.snapshot(window, cx).scroll_position(),
 2294            gpui::Point::new(0., 6.)
 2295        );
 2296        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2297        assert_eq!(
 2298            editor.snapshot(window, cx).scroll_position(),
 2299            gpui::Point::new(0., 3.)
 2300        );
 2301
 2302        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2303        assert_eq!(
 2304            editor.snapshot(window, cx).scroll_position(),
 2305            gpui::Point::new(0., 1.)
 2306        );
 2307        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2308        assert_eq!(
 2309            editor.snapshot(window, cx).scroll_position(),
 2310            gpui::Point::new(0., 3.)
 2311        );
 2312    });
 2313}
 2314
 2315#[gpui::test]
 2316async fn test_autoscroll(cx: &mut TestAppContext) {
 2317    init_test(cx, |_| {});
 2318    let mut cx = EditorTestContext::new(cx).await;
 2319
 2320    let line_height = cx.update_editor(|editor, window, cx| {
 2321        editor.set_vertical_scroll_margin(2, cx);
 2322        editor
 2323            .style()
 2324            .unwrap()
 2325            .text
 2326            .line_height_in_pixels(window.rem_size())
 2327    });
 2328    let window = cx.window;
 2329    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2330
 2331    cx.set_state(
 2332        r#"ˇone
 2333            two
 2334            three
 2335            four
 2336            five
 2337            six
 2338            seven
 2339            eight
 2340            nine
 2341            ten
 2342        "#,
 2343    );
 2344    cx.update_editor(|editor, window, cx| {
 2345        assert_eq!(
 2346            editor.snapshot(window, cx).scroll_position(),
 2347            gpui::Point::new(0., 0.0)
 2348        );
 2349    });
 2350
 2351    // Add a cursor below the visible area. Since both cursors cannot fit
 2352    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2353    // allows the vertical scroll margin below that cursor.
 2354    cx.update_editor(|editor, window, cx| {
 2355        editor.change_selections(Default::default(), window, cx, |selections| {
 2356            selections.select_ranges([
 2357                Point::new(0, 0)..Point::new(0, 0),
 2358                Point::new(6, 0)..Point::new(6, 0),
 2359            ]);
 2360        })
 2361    });
 2362    cx.update_editor(|editor, window, cx| {
 2363        assert_eq!(
 2364            editor.snapshot(window, cx).scroll_position(),
 2365            gpui::Point::new(0., 3.0)
 2366        );
 2367    });
 2368
 2369    // Move down. The editor cursor scrolls down to track the newest cursor.
 2370    cx.update_editor(|editor, window, cx| {
 2371        editor.move_down(&Default::default(), window, cx);
 2372    });
 2373    cx.update_editor(|editor, window, cx| {
 2374        assert_eq!(
 2375            editor.snapshot(window, cx).scroll_position(),
 2376            gpui::Point::new(0., 4.0)
 2377        );
 2378    });
 2379
 2380    // Add a cursor above the visible area. Since both cursors fit on screen,
 2381    // the editor scrolls to show both.
 2382    cx.update_editor(|editor, window, cx| {
 2383        editor.change_selections(Default::default(), window, cx, |selections| {
 2384            selections.select_ranges([
 2385                Point::new(1, 0)..Point::new(1, 0),
 2386                Point::new(6, 0)..Point::new(6, 0),
 2387            ]);
 2388        })
 2389    });
 2390    cx.update_editor(|editor, window, cx| {
 2391        assert_eq!(
 2392            editor.snapshot(window, cx).scroll_position(),
 2393            gpui::Point::new(0., 1.0)
 2394        );
 2395    });
 2396}
 2397
 2398#[gpui::test]
 2399async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2400    init_test(cx, |_| {});
 2401    let mut cx = EditorTestContext::new(cx).await;
 2402
 2403    let line_height = cx.editor(|editor, window, _cx| {
 2404        editor
 2405            .style()
 2406            .unwrap()
 2407            .text
 2408            .line_height_in_pixels(window.rem_size())
 2409    });
 2410    let window = cx.window;
 2411    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2412    cx.set_state(
 2413        &r#"
 2414        ˇone
 2415        two
 2416        threeˇ
 2417        four
 2418        five
 2419        six
 2420        seven
 2421        eight
 2422        nine
 2423        ten
 2424        "#
 2425        .unindent(),
 2426    );
 2427
 2428    cx.update_editor(|editor, window, cx| {
 2429        editor.move_page_down(&MovePageDown::default(), window, cx)
 2430    });
 2431    cx.assert_editor_state(
 2432        &r#"
 2433        one
 2434        two
 2435        three
 2436        ˇfour
 2437        five
 2438        sixˇ
 2439        seven
 2440        eight
 2441        nine
 2442        ten
 2443        "#
 2444        .unindent(),
 2445    );
 2446
 2447    cx.update_editor(|editor, window, cx| {
 2448        editor.move_page_down(&MovePageDown::default(), window, cx)
 2449    });
 2450    cx.assert_editor_state(
 2451        &r#"
 2452        one
 2453        two
 2454        three
 2455        four
 2456        five
 2457        six
 2458        ˇseven
 2459        eight
 2460        nineˇ
 2461        ten
 2462        "#
 2463        .unindent(),
 2464    );
 2465
 2466    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2467    cx.assert_editor_state(
 2468        &r#"
 2469        one
 2470        two
 2471        three
 2472        ˇfour
 2473        five
 2474        sixˇ
 2475        seven
 2476        eight
 2477        nine
 2478        ten
 2479        "#
 2480        .unindent(),
 2481    );
 2482
 2483    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2484    cx.assert_editor_state(
 2485        &r#"
 2486        ˇone
 2487        two
 2488        threeˇ
 2489        four
 2490        five
 2491        six
 2492        seven
 2493        eight
 2494        nine
 2495        ten
 2496        "#
 2497        .unindent(),
 2498    );
 2499
 2500    // Test select collapsing
 2501    cx.update_editor(|editor, window, cx| {
 2502        editor.move_page_down(&MovePageDown::default(), window, cx);
 2503        editor.move_page_down(&MovePageDown::default(), window, cx);
 2504        editor.move_page_down(&MovePageDown::default(), window, cx);
 2505    });
 2506    cx.assert_editor_state(
 2507        &r#"
 2508        one
 2509        two
 2510        three
 2511        four
 2512        five
 2513        six
 2514        seven
 2515        eight
 2516        nine
 2517        ˇten
 2518        ˇ"#
 2519        .unindent(),
 2520    );
 2521}
 2522
 2523#[gpui::test]
 2524async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2525    init_test(cx, |_| {});
 2526    let mut cx = EditorTestContext::new(cx).await;
 2527    cx.set_state("one «two threeˇ» four");
 2528    cx.update_editor(|editor, window, cx| {
 2529        editor.delete_to_beginning_of_line(
 2530            &DeleteToBeginningOfLine {
 2531                stop_at_indent: false,
 2532            },
 2533            window,
 2534            cx,
 2535        );
 2536        assert_eq!(editor.text(cx), " four");
 2537    });
 2538}
 2539
 2540#[gpui::test]
 2541async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2542    init_test(cx, |_| {});
 2543
 2544    let mut cx = EditorTestContext::new(cx).await;
 2545
 2546    // For an empty selection, the preceding word fragment is deleted.
 2547    // For non-empty selections, only selected characters are deleted.
 2548    cx.set_state("onˇe two t«hreˇ»e four");
 2549    cx.update_editor(|editor, window, cx| {
 2550        editor.delete_to_previous_word_start(
 2551            &DeleteToPreviousWordStart {
 2552                ignore_newlines: false,
 2553                ignore_brackets: false,
 2554            },
 2555            window,
 2556            cx,
 2557        );
 2558    });
 2559    cx.assert_editor_state("ˇe two tˇe four");
 2560
 2561    cx.set_state("e tˇwo te «fˇ»our");
 2562    cx.update_editor(|editor, window, cx| {
 2563        editor.delete_to_next_word_end(
 2564            &DeleteToNextWordEnd {
 2565                ignore_newlines: false,
 2566                ignore_brackets: false,
 2567            },
 2568            window,
 2569            cx,
 2570        );
 2571    });
 2572    cx.assert_editor_state("e tˇ te ˇour");
 2573}
 2574
 2575#[gpui::test]
 2576async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2577    init_test(cx, |_| {});
 2578
 2579    let mut cx = EditorTestContext::new(cx).await;
 2580
 2581    cx.set_state("here is some text    ˇwith a space");
 2582    cx.update_editor(|editor, window, cx| {
 2583        editor.delete_to_previous_word_start(
 2584            &DeleteToPreviousWordStart {
 2585                ignore_newlines: false,
 2586                ignore_brackets: true,
 2587            },
 2588            window,
 2589            cx,
 2590        );
 2591    });
 2592    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2593    cx.assert_editor_state("here is some textˇwith a space");
 2594
 2595    cx.set_state("here is some text    ˇwith a space");
 2596    cx.update_editor(|editor, window, cx| {
 2597        editor.delete_to_previous_word_start(
 2598            &DeleteToPreviousWordStart {
 2599                ignore_newlines: false,
 2600                ignore_brackets: false,
 2601            },
 2602            window,
 2603            cx,
 2604        );
 2605    });
 2606    cx.assert_editor_state("here is some textˇwith a space");
 2607
 2608    cx.set_state("here is some textˇ    with a space");
 2609    cx.update_editor(|editor, window, cx| {
 2610        editor.delete_to_next_word_end(
 2611            &DeleteToNextWordEnd {
 2612                ignore_newlines: false,
 2613                ignore_brackets: true,
 2614            },
 2615            window,
 2616            cx,
 2617        );
 2618    });
 2619    // Same happens in the other direction.
 2620    cx.assert_editor_state("here is some textˇwith a space");
 2621
 2622    cx.set_state("here is some textˇ    with a space");
 2623    cx.update_editor(|editor, window, cx| {
 2624        editor.delete_to_next_word_end(
 2625            &DeleteToNextWordEnd {
 2626                ignore_newlines: false,
 2627                ignore_brackets: false,
 2628            },
 2629            window,
 2630            cx,
 2631        );
 2632    });
 2633    cx.assert_editor_state("here is some textˇwith a space");
 2634
 2635    cx.set_state("here is some textˇ    with a space");
 2636    cx.update_editor(|editor, window, cx| {
 2637        editor.delete_to_next_word_end(
 2638            &DeleteToNextWordEnd {
 2639                ignore_newlines: true,
 2640                ignore_brackets: false,
 2641            },
 2642            window,
 2643            cx,
 2644        );
 2645    });
 2646    cx.assert_editor_state("here is some textˇwith a space");
 2647    cx.update_editor(|editor, window, cx| {
 2648        editor.delete_to_previous_word_start(
 2649            &DeleteToPreviousWordStart {
 2650                ignore_newlines: true,
 2651                ignore_brackets: false,
 2652            },
 2653            window,
 2654            cx,
 2655        );
 2656    });
 2657    cx.assert_editor_state("here is some ˇwith a space");
 2658    cx.update_editor(|editor, window, cx| {
 2659        editor.delete_to_previous_word_start(
 2660            &DeleteToPreviousWordStart {
 2661                ignore_newlines: true,
 2662                ignore_brackets: false,
 2663            },
 2664            window,
 2665            cx,
 2666        );
 2667    });
 2668    // Single whitespaces are removed with the word behind them.
 2669    cx.assert_editor_state("here is ˇwith a space");
 2670    cx.update_editor(|editor, window, cx| {
 2671        editor.delete_to_previous_word_start(
 2672            &DeleteToPreviousWordStart {
 2673                ignore_newlines: true,
 2674                ignore_brackets: false,
 2675            },
 2676            window,
 2677            cx,
 2678        );
 2679    });
 2680    cx.assert_editor_state("here ˇwith a space");
 2681    cx.update_editor(|editor, window, cx| {
 2682        editor.delete_to_previous_word_start(
 2683            &DeleteToPreviousWordStart {
 2684                ignore_newlines: true,
 2685                ignore_brackets: false,
 2686            },
 2687            window,
 2688            cx,
 2689        );
 2690    });
 2691    cx.assert_editor_state("ˇwith a space");
 2692    cx.update_editor(|editor, window, cx| {
 2693        editor.delete_to_previous_word_start(
 2694            &DeleteToPreviousWordStart {
 2695                ignore_newlines: true,
 2696                ignore_brackets: false,
 2697            },
 2698            window,
 2699            cx,
 2700        );
 2701    });
 2702    cx.assert_editor_state("ˇwith a space");
 2703    cx.update_editor(|editor, window, cx| {
 2704        editor.delete_to_next_word_end(
 2705            &DeleteToNextWordEnd {
 2706                ignore_newlines: true,
 2707                ignore_brackets: false,
 2708            },
 2709            window,
 2710            cx,
 2711        );
 2712    });
 2713    // Same happens in the other direction.
 2714    cx.assert_editor_state("ˇ a space");
 2715    cx.update_editor(|editor, window, cx| {
 2716        editor.delete_to_next_word_end(
 2717            &DeleteToNextWordEnd {
 2718                ignore_newlines: true,
 2719                ignore_brackets: false,
 2720            },
 2721            window,
 2722            cx,
 2723        );
 2724    });
 2725    cx.assert_editor_state("ˇ space");
 2726    cx.update_editor(|editor, window, cx| {
 2727        editor.delete_to_next_word_end(
 2728            &DeleteToNextWordEnd {
 2729                ignore_newlines: true,
 2730                ignore_brackets: false,
 2731            },
 2732            window,
 2733            cx,
 2734        );
 2735    });
 2736    cx.assert_editor_state("ˇ");
 2737    cx.update_editor(|editor, window, cx| {
 2738        editor.delete_to_next_word_end(
 2739            &DeleteToNextWordEnd {
 2740                ignore_newlines: true,
 2741                ignore_brackets: false,
 2742            },
 2743            window,
 2744            cx,
 2745        );
 2746    });
 2747    cx.assert_editor_state("ˇ");
 2748    cx.update_editor(|editor, window, cx| {
 2749        editor.delete_to_previous_word_start(
 2750            &DeleteToPreviousWordStart {
 2751                ignore_newlines: true,
 2752                ignore_brackets: false,
 2753            },
 2754            window,
 2755            cx,
 2756        );
 2757    });
 2758    cx.assert_editor_state("ˇ");
 2759}
 2760
 2761#[gpui::test]
 2762async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2763    init_test(cx, |_| {});
 2764
 2765    let language = Arc::new(
 2766        Language::new(
 2767            LanguageConfig {
 2768                brackets: BracketPairConfig {
 2769                    pairs: vec![
 2770                        BracketPair {
 2771                            start: "\"".to_string(),
 2772                            end: "\"".to_string(),
 2773                            close: true,
 2774                            surround: true,
 2775                            newline: false,
 2776                        },
 2777                        BracketPair {
 2778                            start: "(".to_string(),
 2779                            end: ")".to_string(),
 2780                            close: true,
 2781                            surround: true,
 2782                            newline: true,
 2783                        },
 2784                    ],
 2785                    ..BracketPairConfig::default()
 2786                },
 2787                ..LanguageConfig::default()
 2788            },
 2789            Some(tree_sitter_rust::LANGUAGE.into()),
 2790        )
 2791        .with_brackets_query(
 2792            r#"
 2793                ("(" @open ")" @close)
 2794                ("\"" @open "\"" @close)
 2795            "#,
 2796        )
 2797        .unwrap(),
 2798    );
 2799
 2800    let mut cx = EditorTestContext::new(cx).await;
 2801    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2802
 2803    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2804    cx.update_editor(|editor, window, cx| {
 2805        editor.delete_to_previous_word_start(
 2806            &DeleteToPreviousWordStart {
 2807                ignore_newlines: true,
 2808                ignore_brackets: false,
 2809            },
 2810            window,
 2811            cx,
 2812        );
 2813    });
 2814    // Deletion stops before brackets if asked to not ignore them.
 2815    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2816    cx.update_editor(|editor, window, cx| {
 2817        editor.delete_to_previous_word_start(
 2818            &DeleteToPreviousWordStart {
 2819                ignore_newlines: true,
 2820                ignore_brackets: false,
 2821            },
 2822            window,
 2823            cx,
 2824        );
 2825    });
 2826    // Deletion has to remove a single bracket and then stop again.
 2827    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2828
 2829    cx.update_editor(|editor, window, cx| {
 2830        editor.delete_to_previous_word_start(
 2831            &DeleteToPreviousWordStart {
 2832                ignore_newlines: true,
 2833                ignore_brackets: false,
 2834            },
 2835            window,
 2836            cx,
 2837        );
 2838    });
 2839    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2840
 2841    cx.update_editor(|editor, window, cx| {
 2842        editor.delete_to_previous_word_start(
 2843            &DeleteToPreviousWordStart {
 2844                ignore_newlines: true,
 2845                ignore_brackets: false,
 2846            },
 2847            window,
 2848            cx,
 2849        );
 2850    });
 2851    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2852
 2853    cx.update_editor(|editor, window, cx| {
 2854        editor.delete_to_previous_word_start(
 2855            &DeleteToPreviousWordStart {
 2856                ignore_newlines: true,
 2857                ignore_brackets: false,
 2858            },
 2859            window,
 2860            cx,
 2861        );
 2862    });
 2863    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2864
 2865    cx.update_editor(|editor, window, cx| {
 2866        editor.delete_to_next_word_end(
 2867            &DeleteToNextWordEnd {
 2868                ignore_newlines: true,
 2869                ignore_brackets: false,
 2870            },
 2871            window,
 2872            cx,
 2873        );
 2874    });
 2875    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2876    cx.assert_editor_state(r#"ˇ");"#);
 2877
 2878    cx.update_editor(|editor, window, cx| {
 2879        editor.delete_to_next_word_end(
 2880            &DeleteToNextWordEnd {
 2881                ignore_newlines: true,
 2882                ignore_brackets: false,
 2883            },
 2884            window,
 2885            cx,
 2886        );
 2887    });
 2888    cx.assert_editor_state(r#"ˇ"#);
 2889
 2890    cx.update_editor(|editor, window, cx| {
 2891        editor.delete_to_next_word_end(
 2892            &DeleteToNextWordEnd {
 2893                ignore_newlines: true,
 2894                ignore_brackets: false,
 2895            },
 2896            window,
 2897            cx,
 2898        );
 2899    });
 2900    cx.assert_editor_state(r#"ˇ"#);
 2901
 2902    cx.set_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: true,
 2908            },
 2909            window,
 2910            cx,
 2911        );
 2912    });
 2913    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2914}
 2915
 2916#[gpui::test]
 2917fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2918    init_test(cx, |_| {});
 2919
 2920    let editor = cx.add_window(|window, cx| {
 2921        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2922        build_editor(buffer, window, cx)
 2923    });
 2924    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2925        ignore_newlines: false,
 2926        ignore_brackets: false,
 2927    };
 2928    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2929        ignore_newlines: true,
 2930        ignore_brackets: false,
 2931    };
 2932
 2933    _ = editor.update(cx, |editor, window, cx| {
 2934        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2935            s.select_display_ranges([
 2936                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2937            ])
 2938        });
 2939        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2940        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2941        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2942        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2943        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2944        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2945        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2946        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2947        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2948        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2949        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2950        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2951    });
 2952}
 2953
 2954#[gpui::test]
 2955fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2956    init_test(cx, |_| {});
 2957
 2958    let editor = cx.add_window(|window, cx| {
 2959        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2960        build_editor(buffer, window, cx)
 2961    });
 2962    let del_to_next_word_end = DeleteToNextWordEnd {
 2963        ignore_newlines: false,
 2964        ignore_brackets: false,
 2965    };
 2966    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2967        ignore_newlines: true,
 2968        ignore_brackets: false,
 2969    };
 2970
 2971    _ = editor.update(cx, |editor, window, cx| {
 2972        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2973            s.select_display_ranges([
 2974                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2975            ])
 2976        });
 2977        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2978        assert_eq!(
 2979            editor.buffer.read(cx).read(cx).text(),
 2980            "one\n   two\nthree\n   four"
 2981        );
 2982        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2983        assert_eq!(
 2984            editor.buffer.read(cx).read(cx).text(),
 2985            "\n   two\nthree\n   four"
 2986        );
 2987        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2988        assert_eq!(
 2989            editor.buffer.read(cx).read(cx).text(),
 2990            "two\nthree\n   four"
 2991        );
 2992        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2993        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2994        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2995        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2996        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2997        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 2998        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2999        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3000    });
 3001}
 3002
 3003#[gpui::test]
 3004fn test_newline(cx: &mut TestAppContext) {
 3005    init_test(cx, |_| {});
 3006
 3007    let editor = cx.add_window(|window, cx| {
 3008        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3009        build_editor(buffer, window, cx)
 3010    });
 3011
 3012    _ = editor.update(cx, |editor, window, cx| {
 3013        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3014            s.select_display_ranges([
 3015                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3016                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3017                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3018            ])
 3019        });
 3020
 3021        editor.newline(&Newline, window, cx);
 3022        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3023    });
 3024}
 3025
 3026#[gpui::test]
 3027fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3028    init_test(cx, |_| {});
 3029
 3030    let editor = cx.add_window(|window, cx| {
 3031        let buffer = MultiBuffer::build_simple(
 3032            "
 3033                a
 3034                b(
 3035                    X
 3036                )
 3037                c(
 3038                    X
 3039                )
 3040            "
 3041            .unindent()
 3042            .as_str(),
 3043            cx,
 3044        );
 3045        let mut editor = build_editor(buffer, window, cx);
 3046        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3047            s.select_ranges([
 3048                Point::new(2, 4)..Point::new(2, 5),
 3049                Point::new(5, 4)..Point::new(5, 5),
 3050            ])
 3051        });
 3052        editor
 3053    });
 3054
 3055    _ = editor.update(cx, |editor, window, cx| {
 3056        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3057        editor.buffer.update(cx, |buffer, cx| {
 3058            buffer.edit(
 3059                [
 3060                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3061                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3062                ],
 3063                None,
 3064                cx,
 3065            );
 3066            assert_eq!(
 3067                buffer.read(cx).text(),
 3068                "
 3069                    a
 3070                    b()
 3071                    c()
 3072                "
 3073                .unindent()
 3074            );
 3075        });
 3076        assert_eq!(
 3077            editor.selections.ranges(cx),
 3078            &[
 3079                Point::new(1, 2)..Point::new(1, 2),
 3080                Point::new(2, 2)..Point::new(2, 2),
 3081            ],
 3082        );
 3083
 3084        editor.newline(&Newline, window, cx);
 3085        assert_eq!(
 3086            editor.text(cx),
 3087            "
 3088                a
 3089                b(
 3090                )
 3091                c(
 3092                )
 3093            "
 3094            .unindent()
 3095        );
 3096
 3097        // The selections are moved after the inserted newlines
 3098        assert_eq!(
 3099            editor.selections.ranges(cx),
 3100            &[
 3101                Point::new(2, 0)..Point::new(2, 0),
 3102                Point::new(4, 0)..Point::new(4, 0),
 3103            ],
 3104        );
 3105    });
 3106}
 3107
 3108#[gpui::test]
 3109async fn test_newline_above(cx: &mut TestAppContext) {
 3110    init_test(cx, |settings| {
 3111        settings.defaults.tab_size = NonZeroU32::new(4)
 3112    });
 3113
 3114    let language = Arc::new(
 3115        Language::new(
 3116            LanguageConfig::default(),
 3117            Some(tree_sitter_rust::LANGUAGE.into()),
 3118        )
 3119        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3120        .unwrap(),
 3121    );
 3122
 3123    let mut cx = EditorTestContext::new(cx).await;
 3124    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3125    cx.set_state(indoc! {"
 3126        const a: ˇA = (
 3127 3128                «const_functionˇ»(ˇ),
 3129                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3130 3131        ˇ);ˇ
 3132    "});
 3133
 3134    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3135    cx.assert_editor_state(indoc! {"
 3136        ˇ
 3137        const a: A = (
 3138            ˇ
 3139            (
 3140                ˇ
 3141                ˇ
 3142                const_function(),
 3143                ˇ
 3144                ˇ
 3145                ˇ
 3146                ˇ
 3147                something_else,
 3148                ˇ
 3149            )
 3150            ˇ
 3151            ˇ
 3152        );
 3153    "});
 3154}
 3155
 3156#[gpui::test]
 3157async fn test_newline_below(cx: &mut TestAppContext) {
 3158    init_test(cx, |settings| {
 3159        settings.defaults.tab_size = NonZeroU32::new(4)
 3160    });
 3161
 3162    let language = Arc::new(
 3163        Language::new(
 3164            LanguageConfig::default(),
 3165            Some(tree_sitter_rust::LANGUAGE.into()),
 3166        )
 3167        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3168        .unwrap(),
 3169    );
 3170
 3171    let mut cx = EditorTestContext::new(cx).await;
 3172    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3173    cx.set_state(indoc! {"
 3174        const a: ˇA = (
 3175 3176                «const_functionˇ»(ˇ),
 3177                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3178 3179        ˇ);ˇ
 3180    "});
 3181
 3182    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3183    cx.assert_editor_state(indoc! {"
 3184        const a: A = (
 3185            ˇ
 3186            (
 3187                ˇ
 3188                const_function(),
 3189                ˇ
 3190                ˇ
 3191                something_else,
 3192                ˇ
 3193                ˇ
 3194                ˇ
 3195                ˇ
 3196            )
 3197            ˇ
 3198        );
 3199        ˇ
 3200        ˇ
 3201    "});
 3202}
 3203
 3204#[gpui::test]
 3205async fn test_newline_comments(cx: &mut TestAppContext) {
 3206    init_test(cx, |settings| {
 3207        settings.defaults.tab_size = NonZeroU32::new(4)
 3208    });
 3209
 3210    let language = Arc::new(Language::new(
 3211        LanguageConfig {
 3212            line_comments: vec!["// ".into()],
 3213            ..LanguageConfig::default()
 3214        },
 3215        None,
 3216    ));
 3217    {
 3218        let mut cx = EditorTestContext::new(cx).await;
 3219        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3220        cx.set_state(indoc! {"
 3221        // Fooˇ
 3222    "});
 3223
 3224        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3225        cx.assert_editor_state(indoc! {"
 3226        // Foo
 3227        // ˇ
 3228    "});
 3229        // Ensure that we add comment prefix when existing line contains space
 3230        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3231        cx.assert_editor_state(
 3232            indoc! {"
 3233        // Foo
 3234        //s
 3235        // ˇ
 3236    "}
 3237            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3238            .as_str(),
 3239        );
 3240        // Ensure that we add comment prefix when existing line does not contain space
 3241        cx.set_state(indoc! {"
 3242        // Foo
 3243        //ˇ
 3244    "});
 3245        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3246        cx.assert_editor_state(indoc! {"
 3247        // Foo
 3248        //
 3249        // ˇ
 3250    "});
 3251        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3252        cx.set_state(indoc! {"
 3253        ˇ// Foo
 3254    "});
 3255        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3256        cx.assert_editor_state(indoc! {"
 3257
 3258        ˇ// Foo
 3259    "});
 3260    }
 3261    // Ensure that comment continuations can be disabled.
 3262    update_test_language_settings(cx, |settings| {
 3263        settings.defaults.extend_comment_on_newline = Some(false);
 3264    });
 3265    let mut cx = EditorTestContext::new(cx).await;
 3266    cx.set_state(indoc! {"
 3267        // Fooˇ
 3268    "});
 3269    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3270    cx.assert_editor_state(indoc! {"
 3271        // Foo
 3272        ˇ
 3273    "});
 3274}
 3275
 3276#[gpui::test]
 3277async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3278    init_test(cx, |settings| {
 3279        settings.defaults.tab_size = NonZeroU32::new(4)
 3280    });
 3281
 3282    let language = Arc::new(Language::new(
 3283        LanguageConfig {
 3284            line_comments: vec!["// ".into(), "/// ".into()],
 3285            ..LanguageConfig::default()
 3286        },
 3287        None,
 3288    ));
 3289    {
 3290        let mut cx = EditorTestContext::new(cx).await;
 3291        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3292        cx.set_state(indoc! {"
 3293        //ˇ
 3294    "});
 3295        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3296        cx.assert_editor_state(indoc! {"
 3297        //
 3298        // ˇ
 3299    "});
 3300
 3301        cx.set_state(indoc! {"
 3302        ///ˇ
 3303    "});
 3304        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3305        cx.assert_editor_state(indoc! {"
 3306        ///
 3307        /// ˇ
 3308    "});
 3309    }
 3310}
 3311
 3312#[gpui::test]
 3313async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3314    init_test(cx, |settings| {
 3315        settings.defaults.tab_size = NonZeroU32::new(4)
 3316    });
 3317
 3318    let language = Arc::new(
 3319        Language::new(
 3320            LanguageConfig {
 3321                documentation_comment: Some(language::BlockCommentConfig {
 3322                    start: "/**".into(),
 3323                    end: "*/".into(),
 3324                    prefix: "* ".into(),
 3325                    tab_size: 1,
 3326                }),
 3327
 3328                ..LanguageConfig::default()
 3329            },
 3330            Some(tree_sitter_rust::LANGUAGE.into()),
 3331        )
 3332        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3333        .unwrap(),
 3334    );
 3335
 3336    {
 3337        let mut cx = EditorTestContext::new(cx).await;
 3338        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3339        cx.set_state(indoc! {"
 3340        /**ˇ
 3341    "});
 3342
 3343        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3344        cx.assert_editor_state(indoc! {"
 3345        /**
 3346         * ˇ
 3347    "});
 3348        // Ensure that if cursor is before the comment start,
 3349        // we do not actually insert a comment prefix.
 3350        cx.set_state(indoc! {"
 3351        ˇ/**
 3352    "});
 3353        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3354        cx.assert_editor_state(indoc! {"
 3355
 3356        ˇ/**
 3357    "});
 3358        // Ensure that if cursor is between it doesn't add comment prefix.
 3359        cx.set_state(indoc! {"
 3360        /*ˇ*
 3361    "});
 3362        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3363        cx.assert_editor_state(indoc! {"
 3364        /*
 3365        ˇ*
 3366    "});
 3367        // Ensure that if suffix exists on same line after cursor it adds new line.
 3368        cx.set_state(indoc! {"
 3369        /**ˇ*/
 3370    "});
 3371        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3372        cx.assert_editor_state(indoc! {"
 3373        /**
 3374         * ˇ
 3375         */
 3376    "});
 3377        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3378        cx.set_state(indoc! {"
 3379        /**ˇ */
 3380    "});
 3381        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3382        cx.assert_editor_state(indoc! {"
 3383        /**
 3384         * ˇ
 3385         */
 3386    "});
 3387        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3388        cx.set_state(indoc! {"
 3389        /** ˇ*/
 3390    "});
 3391        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3392        cx.assert_editor_state(
 3393            indoc! {"
 3394        /**s
 3395         * ˇ
 3396         */
 3397    "}
 3398            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3399            .as_str(),
 3400        );
 3401        // Ensure that delimiter space is preserved when newline on already
 3402        // spaced delimiter.
 3403        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3404        cx.assert_editor_state(
 3405            indoc! {"
 3406        /**s
 3407         *s
 3408         * ˇ
 3409         */
 3410    "}
 3411            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3412            .as_str(),
 3413        );
 3414        // Ensure that delimiter space is preserved when space is not
 3415        // on existing delimiter.
 3416        cx.set_state(indoc! {"
 3417        /**
 3418 3419         */
 3420    "});
 3421        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3422        cx.assert_editor_state(indoc! {"
 3423        /**
 3424         *
 3425         * ˇ
 3426         */
 3427    "});
 3428        // Ensure that if suffix exists on same line after cursor it
 3429        // doesn't add extra new line if prefix is not on same line.
 3430        cx.set_state(indoc! {"
 3431        /**
 3432        ˇ*/
 3433    "});
 3434        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3435        cx.assert_editor_state(indoc! {"
 3436        /**
 3437
 3438        ˇ*/
 3439    "});
 3440        // Ensure that it detects suffix after existing prefix.
 3441        cx.set_state(indoc! {"
 3442        /**ˇ/
 3443    "});
 3444        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3445        cx.assert_editor_state(indoc! {"
 3446        /**
 3447        ˇ/
 3448    "});
 3449        // Ensure that if suffix exists on same line before
 3450        // cursor it does not add comment prefix.
 3451        cx.set_state(indoc! {"
 3452        /** */ˇ
 3453    "});
 3454        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3455        cx.assert_editor_state(indoc! {"
 3456        /** */
 3457        ˇ
 3458    "});
 3459        // Ensure that if suffix exists on same line before
 3460        // cursor it does not add comment prefix.
 3461        cx.set_state(indoc! {"
 3462        /**
 3463         *
 3464         */ˇ
 3465    "});
 3466        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3467        cx.assert_editor_state(indoc! {"
 3468        /**
 3469         *
 3470         */
 3471         ˇ
 3472    "});
 3473
 3474        // Ensure that inline comment followed by code
 3475        // doesn't add comment prefix on newline
 3476        cx.set_state(indoc! {"
 3477        /** */ textˇ
 3478    "});
 3479        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3480        cx.assert_editor_state(indoc! {"
 3481        /** */ text
 3482        ˇ
 3483    "});
 3484
 3485        // Ensure that text after comment end tag
 3486        // doesn't add comment prefix on newline
 3487        cx.set_state(indoc! {"
 3488        /**
 3489         *
 3490         */ˇtext
 3491    "});
 3492        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3493        cx.assert_editor_state(indoc! {"
 3494        /**
 3495         *
 3496         */
 3497         ˇtext
 3498    "});
 3499
 3500        // Ensure if not comment block it doesn't
 3501        // add comment prefix on newline
 3502        cx.set_state(indoc! {"
 3503        * textˇ
 3504    "});
 3505        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3506        cx.assert_editor_state(indoc! {"
 3507        * text
 3508        ˇ
 3509    "});
 3510    }
 3511    // Ensure that comment continuations can be disabled.
 3512    update_test_language_settings(cx, |settings| {
 3513        settings.defaults.extend_comment_on_newline = Some(false);
 3514    });
 3515    let mut cx = EditorTestContext::new(cx).await;
 3516    cx.set_state(indoc! {"
 3517        /**ˇ
 3518    "});
 3519    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3520    cx.assert_editor_state(indoc! {"
 3521        /**
 3522        ˇ
 3523    "});
 3524}
 3525
 3526#[gpui::test]
 3527async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3528    init_test(cx, |settings| {
 3529        settings.defaults.tab_size = NonZeroU32::new(4)
 3530    });
 3531
 3532    let lua_language = Arc::new(Language::new(
 3533        LanguageConfig {
 3534            line_comments: vec!["--".into()],
 3535            block_comment: Some(language::BlockCommentConfig {
 3536                start: "--[[".into(),
 3537                prefix: "".into(),
 3538                end: "]]".into(),
 3539                tab_size: 0,
 3540            }),
 3541            ..LanguageConfig::default()
 3542        },
 3543        None,
 3544    ));
 3545
 3546    let mut cx = EditorTestContext::new(cx).await;
 3547    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3548
 3549    // Line with line comment should extend
 3550    cx.set_state(indoc! {"
 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    // Line with block comment that matches line comment should not extend
 3560    cx.set_state(indoc! {"
 3561        --[[ˇ
 3562    "});
 3563    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3564    cx.assert_editor_state(indoc! {"
 3565        --[[
 3566        ˇ
 3567    "});
 3568}
 3569
 3570#[gpui::test]
 3571fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3572    init_test(cx, |_| {});
 3573
 3574    let editor = cx.add_window(|window, cx| {
 3575        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3576        let mut editor = build_editor(buffer, window, cx);
 3577        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3578            s.select_ranges([3..4, 11..12, 19..20])
 3579        });
 3580        editor
 3581    });
 3582
 3583    _ = editor.update(cx, |editor, window, cx| {
 3584        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3585        editor.buffer.update(cx, |buffer, cx| {
 3586            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3587            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3588        });
 3589        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3590
 3591        editor.insert("Z", window, cx);
 3592        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3593
 3594        // The selections are moved after the inserted characters
 3595        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3596    });
 3597}
 3598
 3599#[gpui::test]
 3600async fn test_tab(cx: &mut TestAppContext) {
 3601    init_test(cx, |settings| {
 3602        settings.defaults.tab_size = NonZeroU32::new(3)
 3603    });
 3604
 3605    let mut cx = EditorTestContext::new(cx).await;
 3606    cx.set_state(indoc! {"
 3607        ˇabˇc
 3608        ˇ🏀ˇ🏀ˇefg
 3609 3610    "});
 3611    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3612    cx.assert_editor_state(indoc! {"
 3613           ˇab ˇc
 3614           ˇ🏀  ˇ🏀  ˇefg
 3615        d  ˇ
 3616    "});
 3617
 3618    cx.set_state(indoc! {"
 3619        a
 3620        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3621    "});
 3622    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3623    cx.assert_editor_state(indoc! {"
 3624        a
 3625           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3626    "});
 3627}
 3628
 3629#[gpui::test]
 3630async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3631    init_test(cx, |_| {});
 3632
 3633    let mut cx = EditorTestContext::new(cx).await;
 3634    let language = Arc::new(
 3635        Language::new(
 3636            LanguageConfig::default(),
 3637            Some(tree_sitter_rust::LANGUAGE.into()),
 3638        )
 3639        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3640        .unwrap(),
 3641    );
 3642    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3643
 3644    // test when all cursors are not at suggested indent
 3645    // then simply move to their suggested indent location
 3646    cx.set_state(indoc! {"
 3647        const a: B = (
 3648            c(
 3649        ˇ
 3650        ˇ    )
 3651        );
 3652    "});
 3653    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3654    cx.assert_editor_state(indoc! {"
 3655        const a: B = (
 3656            c(
 3657                ˇ
 3658            ˇ)
 3659        );
 3660    "});
 3661
 3662    // test cursor already at suggested indent not moving when
 3663    // other cursors are yet to reach their suggested indents
 3664    cx.set_state(indoc! {"
 3665        ˇ
 3666        const a: B = (
 3667            c(
 3668                d(
 3669        ˇ
 3670                )
 3671        ˇ
 3672        ˇ    )
 3673        );
 3674    "});
 3675    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3676    cx.assert_editor_state(indoc! {"
 3677        ˇ
 3678        const a: B = (
 3679            c(
 3680                d(
 3681                    ˇ
 3682                )
 3683                ˇ
 3684            ˇ)
 3685        );
 3686    "});
 3687    // test when all cursors are at suggested indent then tab is inserted
 3688    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3689    cx.assert_editor_state(indoc! {"
 3690            ˇ
 3691        const a: B = (
 3692            c(
 3693                d(
 3694                        ˇ
 3695                )
 3696                    ˇ
 3697                ˇ)
 3698        );
 3699    "});
 3700
 3701    // test when current indent is less than suggested indent,
 3702    // we adjust line to match suggested indent and move cursor to it
 3703    //
 3704    // when no other cursor is at word boundary, all of them should move
 3705    cx.set_state(indoc! {"
 3706        const a: B = (
 3707            c(
 3708                d(
 3709        ˇ
 3710        ˇ   )
 3711        ˇ   )
 3712        );
 3713    "});
 3714    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3715    cx.assert_editor_state(indoc! {"
 3716        const a: B = (
 3717            c(
 3718                d(
 3719                    ˇ
 3720                ˇ)
 3721            ˇ)
 3722        );
 3723    "});
 3724
 3725    // test when current indent is less than suggested indent,
 3726    // we adjust line to match suggested indent and move cursor to it
 3727    //
 3728    // when some other cursor is at word boundary, it should not move
 3729    cx.set_state(indoc! {"
 3730        const a: B = (
 3731            c(
 3732                d(
 3733        ˇ
 3734        ˇ   )
 3735           ˇ)
 3736        );
 3737    "});
 3738    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3739    cx.assert_editor_state(indoc! {"
 3740        const a: B = (
 3741            c(
 3742                d(
 3743                    ˇ
 3744                ˇ)
 3745            ˇ)
 3746        );
 3747    "});
 3748
 3749    // test when current indent is more than suggested indent,
 3750    // we just move cursor to current indent instead of suggested indent
 3751    //
 3752    // when no other cursor is at word boundary, all of them should move
 3753    cx.set_state(indoc! {"
 3754        const a: B = (
 3755            c(
 3756                d(
 3757        ˇ
 3758        ˇ                )
 3759        ˇ   )
 3760        );
 3761    "});
 3762    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3763    cx.assert_editor_state(indoc! {"
 3764        const a: B = (
 3765            c(
 3766                d(
 3767                    ˇ
 3768                        ˇ)
 3769            ˇ)
 3770        );
 3771    "});
 3772    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3773    cx.assert_editor_state(indoc! {"
 3774        const a: B = (
 3775            c(
 3776                d(
 3777                        ˇ
 3778                            ˇ)
 3779                ˇ)
 3780        );
 3781    "});
 3782
 3783    // test when current indent is more than suggested indent,
 3784    // we just move cursor to current indent instead of suggested indent
 3785    //
 3786    // when some other cursor is at word boundary, it doesn't move
 3787    cx.set_state(indoc! {"
 3788        const a: B = (
 3789            c(
 3790                d(
 3791        ˇ
 3792        ˇ                )
 3793            ˇ)
 3794        );
 3795    "});
 3796    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3797    cx.assert_editor_state(indoc! {"
 3798        const a: B = (
 3799            c(
 3800                d(
 3801                    ˇ
 3802                        ˇ)
 3803            ˇ)
 3804        );
 3805    "});
 3806
 3807    // handle auto-indent when there are multiple cursors on the same line
 3808    cx.set_state(indoc! {"
 3809        const a: B = (
 3810            c(
 3811        ˇ    ˇ
 3812        ˇ    )
 3813        );
 3814    "});
 3815    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3816    cx.assert_editor_state(indoc! {"
 3817        const a: B = (
 3818            c(
 3819                ˇ
 3820            ˇ)
 3821        );
 3822    "});
 3823}
 3824
 3825#[gpui::test]
 3826async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3827    init_test(cx, |settings| {
 3828        settings.defaults.tab_size = NonZeroU32::new(3)
 3829    });
 3830
 3831    let mut cx = EditorTestContext::new(cx).await;
 3832    cx.set_state(indoc! {"
 3833         ˇ
 3834        \t ˇ
 3835        \t  ˇ
 3836        \t   ˇ
 3837         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3838    "});
 3839
 3840    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3841    cx.assert_editor_state(indoc! {"
 3842           ˇ
 3843        \t   ˇ
 3844        \t   ˇ
 3845        \t      ˇ
 3846         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3847    "});
 3848}
 3849
 3850#[gpui::test]
 3851async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3852    init_test(cx, |settings| {
 3853        settings.defaults.tab_size = NonZeroU32::new(4)
 3854    });
 3855
 3856    let language = Arc::new(
 3857        Language::new(
 3858            LanguageConfig::default(),
 3859            Some(tree_sitter_rust::LANGUAGE.into()),
 3860        )
 3861        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3862        .unwrap(),
 3863    );
 3864
 3865    let mut cx = EditorTestContext::new(cx).await;
 3866    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3867    cx.set_state(indoc! {"
 3868        fn a() {
 3869            if b {
 3870        \t ˇc
 3871            }
 3872        }
 3873    "});
 3874
 3875    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3876    cx.assert_editor_state(indoc! {"
 3877        fn a() {
 3878            if b {
 3879                ˇc
 3880            }
 3881        }
 3882    "});
 3883}
 3884
 3885#[gpui::test]
 3886async fn test_indent_outdent(cx: &mut TestAppContext) {
 3887    init_test(cx, |settings| {
 3888        settings.defaults.tab_size = NonZeroU32::new(4);
 3889    });
 3890
 3891    let mut cx = EditorTestContext::new(cx).await;
 3892
 3893    cx.set_state(indoc! {"
 3894          «oneˇ» «twoˇ»
 3895        three
 3896         four
 3897    "});
 3898    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3899    cx.assert_editor_state(indoc! {"
 3900            «oneˇ» «twoˇ»
 3901        three
 3902         four
 3903    "});
 3904
 3905    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3906    cx.assert_editor_state(indoc! {"
 3907        «oneˇ» «twoˇ»
 3908        three
 3909         four
 3910    "});
 3911
 3912    // select across line ending
 3913    cx.set_state(indoc! {"
 3914        one two
 3915        t«hree
 3916        ˇ» four
 3917    "});
 3918    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3919    cx.assert_editor_state(indoc! {"
 3920        one two
 3921            t«hree
 3922        ˇ» four
 3923    "});
 3924
 3925    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3926    cx.assert_editor_state(indoc! {"
 3927        one two
 3928        t«hree
 3929        ˇ» four
 3930    "});
 3931
 3932    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3933    cx.set_state(indoc! {"
 3934        one two
 3935        ˇthree
 3936            four
 3937    "});
 3938    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3939    cx.assert_editor_state(indoc! {"
 3940        one two
 3941            ˇthree
 3942            four
 3943    "});
 3944
 3945    cx.set_state(indoc! {"
 3946        one two
 3947        ˇ    three
 3948            four
 3949    "});
 3950    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3951    cx.assert_editor_state(indoc! {"
 3952        one two
 3953        ˇthree
 3954            four
 3955    "});
 3956}
 3957
 3958#[gpui::test]
 3959async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3960    // This is a regression test for issue #33761
 3961    init_test(cx, |_| {});
 3962
 3963    let mut cx = EditorTestContext::new(cx).await;
 3964    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3965    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3966
 3967    cx.set_state(
 3968        r#"ˇ#     ingress:
 3969ˇ#         api:
 3970ˇ#             enabled: false
 3971ˇ#             pathType: Prefix
 3972ˇ#           console:
 3973ˇ#               enabled: false
 3974ˇ#               pathType: Prefix
 3975"#,
 3976    );
 3977
 3978    // Press tab to indent all lines
 3979    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3980
 3981    cx.assert_editor_state(
 3982        r#"    ˇ#     ingress:
 3983    ˇ#         api:
 3984    ˇ#             enabled: false
 3985    ˇ#             pathType: Prefix
 3986    ˇ#           console:
 3987    ˇ#               enabled: false
 3988    ˇ#               pathType: Prefix
 3989"#,
 3990    );
 3991}
 3992
 3993#[gpui::test]
 3994async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3995    // This is a test to make sure our fix for issue #33761 didn't break anything
 3996    init_test(cx, |_| {});
 3997
 3998    let mut cx = EditorTestContext::new(cx).await;
 3999    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4000    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4001
 4002    cx.set_state(
 4003        r#"ˇingress:
 4004ˇ  api:
 4005ˇ    enabled: false
 4006ˇ    pathType: Prefix
 4007"#,
 4008    );
 4009
 4010    // Press tab to indent all lines
 4011    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4012
 4013    cx.assert_editor_state(
 4014        r#"ˇingress:
 4015    ˇapi:
 4016        ˇenabled: false
 4017        ˇpathType: Prefix
 4018"#,
 4019    );
 4020}
 4021
 4022#[gpui::test]
 4023async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4024    init_test(cx, |settings| {
 4025        settings.defaults.hard_tabs = Some(true);
 4026    });
 4027
 4028    let mut cx = EditorTestContext::new(cx).await;
 4029
 4030    // select two ranges on one line
 4031    cx.set_state(indoc! {"
 4032        «oneˇ» «twoˇ»
 4033        three
 4034        four
 4035    "});
 4036    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4037    cx.assert_editor_state(indoc! {"
 4038        \t«oneˇ» «twoˇ»
 4039        three
 4040        four
 4041    "});
 4042    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4043    cx.assert_editor_state(indoc! {"
 4044        \t\t«oneˇ» «twoˇ»
 4045        three
 4046        four
 4047    "});
 4048    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4049    cx.assert_editor_state(indoc! {"
 4050        \t«oneˇ» «twoˇ»
 4051        three
 4052        four
 4053    "});
 4054    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4055    cx.assert_editor_state(indoc! {"
 4056        «oneˇ» «twoˇ»
 4057        three
 4058        four
 4059    "});
 4060
 4061    // select across a line ending
 4062    cx.set_state(indoc! {"
 4063        one two
 4064        t«hree
 4065        ˇ»four
 4066    "});
 4067    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4068    cx.assert_editor_state(indoc! {"
 4069        one two
 4070        \tt«hree
 4071        ˇ»four
 4072    "});
 4073    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4074    cx.assert_editor_state(indoc! {"
 4075        one two
 4076        \t\tt«hree
 4077        ˇ»four
 4078    "});
 4079    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4080    cx.assert_editor_state(indoc! {"
 4081        one two
 4082        \tt«hree
 4083        ˇ»four
 4084    "});
 4085    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4086    cx.assert_editor_state(indoc! {"
 4087        one two
 4088        t«hree
 4089        ˇ»four
 4090    "});
 4091
 4092    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4093    cx.set_state(indoc! {"
 4094        one two
 4095        ˇthree
 4096        four
 4097    "});
 4098    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4099    cx.assert_editor_state(indoc! {"
 4100        one two
 4101        ˇthree
 4102        four
 4103    "});
 4104    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4105    cx.assert_editor_state(indoc! {"
 4106        one two
 4107        \tˇthree
 4108        four
 4109    "});
 4110    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4111    cx.assert_editor_state(indoc! {"
 4112        one two
 4113        ˇthree
 4114        four
 4115    "});
 4116}
 4117
 4118#[gpui::test]
 4119fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4120    init_test(cx, |settings| {
 4121        settings.languages.0.extend([
 4122            (
 4123                "TOML".into(),
 4124                LanguageSettingsContent {
 4125                    tab_size: NonZeroU32::new(2),
 4126                    ..Default::default()
 4127                },
 4128            ),
 4129            (
 4130                "Rust".into(),
 4131                LanguageSettingsContent {
 4132                    tab_size: NonZeroU32::new(4),
 4133                    ..Default::default()
 4134                },
 4135            ),
 4136        ]);
 4137    });
 4138
 4139    let toml_language = Arc::new(Language::new(
 4140        LanguageConfig {
 4141            name: "TOML".into(),
 4142            ..Default::default()
 4143        },
 4144        None,
 4145    ));
 4146    let rust_language = Arc::new(Language::new(
 4147        LanguageConfig {
 4148            name: "Rust".into(),
 4149            ..Default::default()
 4150        },
 4151        None,
 4152    ));
 4153
 4154    let toml_buffer =
 4155        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4156    let rust_buffer =
 4157        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4158    let multibuffer = cx.new(|cx| {
 4159        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4160        multibuffer.push_excerpts(
 4161            toml_buffer.clone(),
 4162            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4163            cx,
 4164        );
 4165        multibuffer.push_excerpts(
 4166            rust_buffer.clone(),
 4167            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4168            cx,
 4169        );
 4170        multibuffer
 4171    });
 4172
 4173    cx.add_window(|window, cx| {
 4174        let mut editor = build_editor(multibuffer, window, cx);
 4175
 4176        assert_eq!(
 4177            editor.text(cx),
 4178            indoc! {"
 4179                a = 1
 4180                b = 2
 4181
 4182                const c: usize = 3;
 4183            "}
 4184        );
 4185
 4186        select_ranges(
 4187            &mut editor,
 4188            indoc! {"
 4189                «aˇ» = 1
 4190                b = 2
 4191
 4192                «const c:ˇ» usize = 3;
 4193            "},
 4194            window,
 4195            cx,
 4196        );
 4197
 4198        editor.tab(&Tab, window, cx);
 4199        assert_text_with_selections(
 4200            &mut editor,
 4201            indoc! {"
 4202                  «aˇ» = 1
 4203                b = 2
 4204
 4205                    «const c:ˇ» usize = 3;
 4206            "},
 4207            cx,
 4208        );
 4209        editor.backtab(&Backtab, window, cx);
 4210        assert_text_with_selections(
 4211            &mut editor,
 4212            indoc! {"
 4213                «aˇ» = 1
 4214                b = 2
 4215
 4216                «const c:ˇ» usize = 3;
 4217            "},
 4218            cx,
 4219        );
 4220
 4221        editor
 4222    });
 4223}
 4224
 4225#[gpui::test]
 4226async fn test_backspace(cx: &mut TestAppContext) {
 4227    init_test(cx, |_| {});
 4228
 4229    let mut cx = EditorTestContext::new(cx).await;
 4230
 4231    // Basic backspace
 4232    cx.set_state(indoc! {"
 4233        onˇe two three
 4234        fou«rˇ» five six
 4235        seven «ˇeight nine
 4236        »ten
 4237    "});
 4238    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4239    cx.assert_editor_state(indoc! {"
 4240        oˇe two three
 4241        fouˇ five six
 4242        seven ˇten
 4243    "});
 4244
 4245    // Test backspace inside and around indents
 4246    cx.set_state(indoc! {"
 4247        zero
 4248            ˇone
 4249                ˇtwo
 4250            ˇ ˇ ˇ  three
 4251        ˇ  ˇ  four
 4252    "});
 4253    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4254    cx.assert_editor_state(indoc! {"
 4255        zero
 4256        ˇone
 4257            ˇtwo
 4258        ˇ  threeˇ  four
 4259    "});
 4260}
 4261
 4262#[gpui::test]
 4263async fn test_delete(cx: &mut TestAppContext) {
 4264    init_test(cx, |_| {});
 4265
 4266    let mut cx = EditorTestContext::new(cx).await;
 4267    cx.set_state(indoc! {"
 4268        onˇe two three
 4269        fou«rˇ» five six
 4270        seven «ˇeight nine
 4271        »ten
 4272    "});
 4273    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4274    cx.assert_editor_state(indoc! {"
 4275        onˇ two three
 4276        fouˇ five six
 4277        seven ˇten
 4278    "});
 4279}
 4280
 4281#[gpui::test]
 4282fn test_delete_line(cx: &mut TestAppContext) {
 4283    init_test(cx, |_| {});
 4284
 4285    let editor = cx.add_window(|window, cx| {
 4286        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4287        build_editor(buffer, window, cx)
 4288    });
 4289    _ = editor.update(cx, |editor, window, cx| {
 4290        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4291            s.select_display_ranges([
 4292                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4293                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4294                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4295            ])
 4296        });
 4297        editor.delete_line(&DeleteLine, window, cx);
 4298        assert_eq!(editor.display_text(cx), "ghi");
 4299        assert_eq!(
 4300            editor.selections.display_ranges(cx),
 4301            vec![
 4302                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4303                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4304            ]
 4305        );
 4306    });
 4307
 4308    let editor = cx.add_window(|window, cx| {
 4309        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4310        build_editor(buffer, window, cx)
 4311    });
 4312    _ = editor.update(cx, |editor, window, cx| {
 4313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4314            s.select_display_ranges([
 4315                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4316            ])
 4317        });
 4318        editor.delete_line(&DeleteLine, window, cx);
 4319        assert_eq!(editor.display_text(cx), "ghi\n");
 4320        assert_eq!(
 4321            editor.selections.display_ranges(cx),
 4322            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4323        );
 4324    });
 4325
 4326    let editor = cx.add_window(|window, cx| {
 4327        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4328        build_editor(buffer, window, cx)
 4329    });
 4330    _ = editor.update(cx, |editor, window, cx| {
 4331        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4332            s.select_display_ranges([
 4333                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4334            ])
 4335        });
 4336        editor.delete_line(&DeleteLine, window, cx);
 4337        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4338        assert_eq!(
 4339            editor.selections.display_ranges(cx),
 4340            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4341        );
 4342    });
 4343}
 4344
 4345#[gpui::test]
 4346fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4347    init_test(cx, |_| {});
 4348
 4349    cx.add_window(|window, cx| {
 4350        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4351        let mut editor = build_editor(buffer.clone(), window, cx);
 4352        let buffer = buffer.read(cx).as_singleton().unwrap();
 4353
 4354        assert_eq!(
 4355            editor.selections.ranges::<Point>(cx),
 4356            &[Point::new(0, 0)..Point::new(0, 0)]
 4357        );
 4358
 4359        // When on single line, replace newline at end by space
 4360        editor.join_lines(&JoinLines, window, cx);
 4361        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4362        assert_eq!(
 4363            editor.selections.ranges::<Point>(cx),
 4364            &[Point::new(0, 3)..Point::new(0, 3)]
 4365        );
 4366
 4367        // When multiple lines are selected, remove newlines that are spanned by the selection
 4368        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4369            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4370        });
 4371        editor.join_lines(&JoinLines, window, cx);
 4372        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4373        assert_eq!(
 4374            editor.selections.ranges::<Point>(cx),
 4375            &[Point::new(0, 11)..Point::new(0, 11)]
 4376        );
 4377
 4378        // Undo should be transactional
 4379        editor.undo(&Undo, window, cx);
 4380        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4381        assert_eq!(
 4382            editor.selections.ranges::<Point>(cx),
 4383            &[Point::new(0, 5)..Point::new(2, 2)]
 4384        );
 4385
 4386        // When joining an empty line don't insert a space
 4387        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4388            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4389        });
 4390        editor.join_lines(&JoinLines, window, cx);
 4391        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4392        assert_eq!(
 4393            editor.selections.ranges::<Point>(cx),
 4394            [Point::new(2, 3)..Point::new(2, 3)]
 4395        );
 4396
 4397        // We can remove trailing newlines
 4398        editor.join_lines(&JoinLines, window, cx);
 4399        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4400        assert_eq!(
 4401            editor.selections.ranges::<Point>(cx),
 4402            [Point::new(2, 3)..Point::new(2, 3)]
 4403        );
 4404
 4405        // We don't blow up on the last line
 4406        editor.join_lines(&JoinLines, window, cx);
 4407        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4408        assert_eq!(
 4409            editor.selections.ranges::<Point>(cx),
 4410            [Point::new(2, 3)..Point::new(2, 3)]
 4411        );
 4412
 4413        // reset to test indentation
 4414        editor.buffer.update(cx, |buffer, cx| {
 4415            buffer.edit(
 4416                [
 4417                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4418                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4419                ],
 4420                None,
 4421                cx,
 4422            )
 4423        });
 4424
 4425        // We remove any leading spaces
 4426        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4427        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4428            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4429        });
 4430        editor.join_lines(&JoinLines, window, cx);
 4431        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4432
 4433        // We don't insert a space for a line containing only spaces
 4434        editor.join_lines(&JoinLines, window, cx);
 4435        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4436
 4437        // We ignore any leading tabs
 4438        editor.join_lines(&JoinLines, window, cx);
 4439        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4440
 4441        editor
 4442    });
 4443}
 4444
 4445#[gpui::test]
 4446fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4447    init_test(cx, |_| {});
 4448
 4449    cx.add_window(|window, cx| {
 4450        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4451        let mut editor = build_editor(buffer.clone(), window, cx);
 4452        let buffer = buffer.read(cx).as_singleton().unwrap();
 4453
 4454        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4455            s.select_ranges([
 4456                Point::new(0, 2)..Point::new(1, 1),
 4457                Point::new(1, 2)..Point::new(1, 2),
 4458                Point::new(3, 1)..Point::new(3, 2),
 4459            ])
 4460        });
 4461
 4462        editor.join_lines(&JoinLines, window, cx);
 4463        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4464
 4465        assert_eq!(
 4466            editor.selections.ranges::<Point>(cx),
 4467            [
 4468                Point::new(0, 7)..Point::new(0, 7),
 4469                Point::new(1, 3)..Point::new(1, 3)
 4470            ]
 4471        );
 4472        editor
 4473    });
 4474}
 4475
 4476#[gpui::test]
 4477async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4478    init_test(cx, |_| {});
 4479
 4480    let mut cx = EditorTestContext::new(cx).await;
 4481
 4482    let diff_base = r#"
 4483        Line 0
 4484        Line 1
 4485        Line 2
 4486        Line 3
 4487        "#
 4488    .unindent();
 4489
 4490    cx.set_state(
 4491        &r#"
 4492        ˇLine 0
 4493        Line 1
 4494        Line 2
 4495        Line 3
 4496        "#
 4497        .unindent(),
 4498    );
 4499
 4500    cx.set_head_text(&diff_base);
 4501    executor.run_until_parked();
 4502
 4503    // Join lines
 4504    cx.update_editor(|editor, window, cx| {
 4505        editor.join_lines(&JoinLines, window, cx);
 4506    });
 4507    executor.run_until_parked();
 4508
 4509    cx.assert_editor_state(
 4510        &r#"
 4511        Line 0ˇ Line 1
 4512        Line 2
 4513        Line 3
 4514        "#
 4515        .unindent(),
 4516    );
 4517    // Join again
 4518    cx.update_editor(|editor, window, cx| {
 4519        editor.join_lines(&JoinLines, window, cx);
 4520    });
 4521    executor.run_until_parked();
 4522
 4523    cx.assert_editor_state(
 4524        &r#"
 4525        Line 0 Line 1ˇ Line 2
 4526        Line 3
 4527        "#
 4528        .unindent(),
 4529    );
 4530}
 4531
 4532#[gpui::test]
 4533async fn test_custom_newlines_cause_no_false_positive_diffs(
 4534    executor: BackgroundExecutor,
 4535    cx: &mut TestAppContext,
 4536) {
 4537    init_test(cx, |_| {});
 4538    let mut cx = EditorTestContext::new(cx).await;
 4539    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4540    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4541    executor.run_until_parked();
 4542
 4543    cx.update_editor(|editor, window, cx| {
 4544        let snapshot = editor.snapshot(window, cx);
 4545        assert_eq!(
 4546            snapshot
 4547                .buffer_snapshot()
 4548                .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
 4549                .collect::<Vec<_>>(),
 4550            Vec::new(),
 4551            "Should not have any diffs for files with custom newlines"
 4552        );
 4553    });
 4554}
 4555
 4556#[gpui::test]
 4557async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4558    init_test(cx, |_| {});
 4559
 4560    let mut cx = EditorTestContext::new(cx).await;
 4561
 4562    // Test sort_lines_case_insensitive()
 4563    cx.set_state(indoc! {"
 4564        «z
 4565        y
 4566        x
 4567        Z
 4568        Y
 4569        Xˇ»
 4570    "});
 4571    cx.update_editor(|e, window, cx| {
 4572        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4573    });
 4574    cx.assert_editor_state(indoc! {"
 4575        «x
 4576        X
 4577        y
 4578        Y
 4579        z
 4580        Zˇ»
 4581    "});
 4582
 4583    // Test sort_lines_by_length()
 4584    //
 4585    // Demonstrates:
 4586    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4587    // - sort is stable
 4588    cx.set_state(indoc! {"
 4589        «123
 4590        æ
 4591        12
 4592 4593        1
 4594        æˇ»
 4595    "});
 4596    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4597    cx.assert_editor_state(indoc! {"
 4598        «æ
 4599 4600        1
 4601        æ
 4602        12
 4603        123ˇ»
 4604    "});
 4605
 4606    // Test reverse_lines()
 4607    cx.set_state(indoc! {"
 4608        «5
 4609        4
 4610        3
 4611        2
 4612        1ˇ»
 4613    "});
 4614    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4615    cx.assert_editor_state(indoc! {"
 4616        «1
 4617        2
 4618        3
 4619        4
 4620        5ˇ»
 4621    "});
 4622
 4623    // Skip testing shuffle_line()
 4624
 4625    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4626    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4627
 4628    // Don't manipulate when cursor is on single line, but expand the selection
 4629    cx.set_state(indoc! {"
 4630        ddˇdd
 4631        ccc
 4632        bb
 4633        a
 4634    "});
 4635    cx.update_editor(|e, window, cx| {
 4636        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4637    });
 4638    cx.assert_editor_state(indoc! {"
 4639        «ddddˇ»
 4640        ccc
 4641        bb
 4642        a
 4643    "});
 4644
 4645    // Basic manipulate case
 4646    // Start selection moves to column 0
 4647    // End of selection shrinks to fit shorter line
 4648    cx.set_state(indoc! {"
 4649        dd«d
 4650        ccc
 4651        bb
 4652        aaaaaˇ»
 4653    "});
 4654    cx.update_editor(|e, window, cx| {
 4655        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4656    });
 4657    cx.assert_editor_state(indoc! {"
 4658        «aaaaa
 4659        bb
 4660        ccc
 4661        dddˇ»
 4662    "});
 4663
 4664    // Manipulate case with newlines
 4665    cx.set_state(indoc! {"
 4666        dd«d
 4667        ccc
 4668
 4669        bb
 4670        aaaaa
 4671
 4672        ˇ»
 4673    "});
 4674    cx.update_editor(|e, window, cx| {
 4675        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4676    });
 4677    cx.assert_editor_state(indoc! {"
 4678        «
 4679
 4680        aaaaa
 4681        bb
 4682        ccc
 4683        dddˇ»
 4684
 4685    "});
 4686
 4687    // Adding new line
 4688    cx.set_state(indoc! {"
 4689        aa«a
 4690        bbˇ»b
 4691    "});
 4692    cx.update_editor(|e, window, cx| {
 4693        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4694    });
 4695    cx.assert_editor_state(indoc! {"
 4696        «aaa
 4697        bbb
 4698        added_lineˇ»
 4699    "});
 4700
 4701    // Removing line
 4702    cx.set_state(indoc! {"
 4703        aa«a
 4704        bbbˇ»
 4705    "});
 4706    cx.update_editor(|e, window, cx| {
 4707        e.manipulate_immutable_lines(window, cx, |lines| {
 4708            lines.pop();
 4709        })
 4710    });
 4711    cx.assert_editor_state(indoc! {"
 4712        «aaaˇ»
 4713    "});
 4714
 4715    // Removing all lines
 4716    cx.set_state(indoc! {"
 4717        aa«a
 4718        bbbˇ»
 4719    "});
 4720    cx.update_editor(|e, window, cx| {
 4721        e.manipulate_immutable_lines(window, cx, |lines| {
 4722            lines.drain(..);
 4723        })
 4724    });
 4725    cx.assert_editor_state(indoc! {"
 4726        ˇ
 4727    "});
 4728}
 4729
 4730#[gpui::test]
 4731async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4732    init_test(cx, |_| {});
 4733
 4734    let mut cx = EditorTestContext::new(cx).await;
 4735
 4736    // Consider continuous selection as single selection
 4737    cx.set_state(indoc! {"
 4738        Aaa«aa
 4739        cˇ»c«c
 4740        bb
 4741        aaaˇ»aa
 4742    "});
 4743    cx.update_editor(|e, window, cx| {
 4744        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4745    });
 4746    cx.assert_editor_state(indoc! {"
 4747        «Aaaaa
 4748        ccc
 4749        bb
 4750        aaaaaˇ»
 4751    "});
 4752
 4753    cx.set_state(indoc! {"
 4754        Aaa«aa
 4755        cˇ»c«c
 4756        bb
 4757        aaaˇ»aa
 4758    "});
 4759    cx.update_editor(|e, window, cx| {
 4760        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4761    });
 4762    cx.assert_editor_state(indoc! {"
 4763        «Aaaaa
 4764        ccc
 4765        bbˇ»
 4766    "});
 4767
 4768    // Consider non continuous selection as distinct dedup operations
 4769    cx.set_state(indoc! {"
 4770        «aaaaa
 4771        bb
 4772        aaaaa
 4773        aaaaaˇ»
 4774
 4775        aaa«aaˇ»
 4776    "});
 4777    cx.update_editor(|e, window, cx| {
 4778        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4779    });
 4780    cx.assert_editor_state(indoc! {"
 4781        «aaaaa
 4782        bbˇ»
 4783
 4784        «aaaaaˇ»
 4785    "});
 4786}
 4787
 4788#[gpui::test]
 4789async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4790    init_test(cx, |_| {});
 4791
 4792    let mut cx = EditorTestContext::new(cx).await;
 4793
 4794    cx.set_state(indoc! {"
 4795        «Aaa
 4796        aAa
 4797        Aaaˇ»
 4798    "});
 4799    cx.update_editor(|e, window, cx| {
 4800        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4801    });
 4802    cx.assert_editor_state(indoc! {"
 4803        «Aaa
 4804        aAaˇ»
 4805    "});
 4806
 4807    cx.set_state(indoc! {"
 4808        «Aaa
 4809        aAa
 4810        aaAˇ»
 4811    "});
 4812    cx.update_editor(|e, window, cx| {
 4813        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4814    });
 4815    cx.assert_editor_state(indoc! {"
 4816        «Aaaˇ»
 4817    "});
 4818}
 4819
 4820#[gpui::test]
 4821async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 4822    init_test(cx, |_| {});
 4823
 4824    let mut cx = EditorTestContext::new(cx).await;
 4825
 4826    let js_language = Arc::new(Language::new(
 4827        LanguageConfig {
 4828            name: "JavaScript".into(),
 4829            wrap_characters: Some(language::WrapCharactersConfig {
 4830                start_prefix: "<".into(),
 4831                start_suffix: ">".into(),
 4832                end_prefix: "</".into(),
 4833                end_suffix: ">".into(),
 4834            }),
 4835            ..LanguageConfig::default()
 4836        },
 4837        None,
 4838    ));
 4839
 4840    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4841
 4842    cx.set_state(indoc! {"
 4843        «testˇ»
 4844    "});
 4845    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4846    cx.assert_editor_state(indoc! {"
 4847        <«ˇ»>test</«ˇ»>
 4848    "});
 4849
 4850    cx.set_state(indoc! {"
 4851        «test
 4852         testˇ»
 4853    "});
 4854    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4855    cx.assert_editor_state(indoc! {"
 4856        <«ˇ»>test
 4857         test</«ˇ»>
 4858    "});
 4859
 4860    cx.set_state(indoc! {"
 4861        teˇst
 4862    "});
 4863    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4864    cx.assert_editor_state(indoc! {"
 4865        te<«ˇ»></«ˇ»>st
 4866    "});
 4867}
 4868
 4869#[gpui::test]
 4870async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 4871    init_test(cx, |_| {});
 4872
 4873    let mut cx = EditorTestContext::new(cx).await;
 4874
 4875    let js_language = Arc::new(Language::new(
 4876        LanguageConfig {
 4877            name: "JavaScript".into(),
 4878            wrap_characters: Some(language::WrapCharactersConfig {
 4879                start_prefix: "<".into(),
 4880                start_suffix: ">".into(),
 4881                end_prefix: "</".into(),
 4882                end_suffix: ">".into(),
 4883            }),
 4884            ..LanguageConfig::default()
 4885        },
 4886        None,
 4887    ));
 4888
 4889    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4890
 4891    cx.set_state(indoc! {"
 4892        «testˇ»
 4893        «testˇ» «testˇ»
 4894        «testˇ»
 4895    "});
 4896    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4897    cx.assert_editor_state(indoc! {"
 4898        <«ˇ»>test</«ˇ»>
 4899        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 4900        <«ˇ»>test</«ˇ»>
 4901    "});
 4902
 4903    cx.set_state(indoc! {"
 4904        «test
 4905         testˇ»
 4906        «test
 4907         testˇ»
 4908    "});
 4909    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4910    cx.assert_editor_state(indoc! {"
 4911        <«ˇ»>test
 4912         test</«ˇ»>
 4913        <«ˇ»>test
 4914         test</«ˇ»>
 4915    "});
 4916}
 4917
 4918#[gpui::test]
 4919async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 4920    init_test(cx, |_| {});
 4921
 4922    let mut cx = EditorTestContext::new(cx).await;
 4923
 4924    let plaintext_language = Arc::new(Language::new(
 4925        LanguageConfig {
 4926            name: "Plain Text".into(),
 4927            ..LanguageConfig::default()
 4928        },
 4929        None,
 4930    ));
 4931
 4932    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 4933
 4934    cx.set_state(indoc! {"
 4935        «testˇ»
 4936    "});
 4937    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4938    cx.assert_editor_state(indoc! {"
 4939      «testˇ»
 4940    "});
 4941}
 4942
 4943#[gpui::test]
 4944async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4945    init_test(cx, |_| {});
 4946
 4947    let mut cx = EditorTestContext::new(cx).await;
 4948
 4949    // Manipulate with multiple selections on a single line
 4950    cx.set_state(indoc! {"
 4951        dd«dd
 4952        cˇ»c«c
 4953        bb
 4954        aaaˇ»aa
 4955    "});
 4956    cx.update_editor(|e, window, cx| {
 4957        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4958    });
 4959    cx.assert_editor_state(indoc! {"
 4960        «aaaaa
 4961        bb
 4962        ccc
 4963        ddddˇ»
 4964    "});
 4965
 4966    // Manipulate with multiple disjoin selections
 4967    cx.set_state(indoc! {"
 4968 4969        4
 4970        3
 4971        2
 4972        1ˇ»
 4973
 4974        dd«dd
 4975        ccc
 4976        bb
 4977        aaaˇ»aa
 4978    "});
 4979    cx.update_editor(|e, window, cx| {
 4980        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4981    });
 4982    cx.assert_editor_state(indoc! {"
 4983        «1
 4984        2
 4985        3
 4986        4
 4987        5ˇ»
 4988
 4989        «aaaaa
 4990        bb
 4991        ccc
 4992        ddddˇ»
 4993    "});
 4994
 4995    // Adding lines on each selection
 4996    cx.set_state(indoc! {"
 4997 4998        1ˇ»
 4999
 5000        bb«bb
 5001        aaaˇ»aa
 5002    "});
 5003    cx.update_editor(|e, window, cx| {
 5004        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5005    });
 5006    cx.assert_editor_state(indoc! {"
 5007        «2
 5008        1
 5009        added lineˇ»
 5010
 5011        «bbbb
 5012        aaaaa
 5013        added lineˇ»
 5014    "});
 5015
 5016    // Removing lines on each selection
 5017    cx.set_state(indoc! {"
 5018 5019        1ˇ»
 5020
 5021        bb«bb
 5022        aaaˇ»aa
 5023    "});
 5024    cx.update_editor(|e, window, cx| {
 5025        e.manipulate_immutable_lines(window, cx, |lines| {
 5026            lines.pop();
 5027        })
 5028    });
 5029    cx.assert_editor_state(indoc! {"
 5030        «2ˇ»
 5031
 5032        «bbbbˇ»
 5033    "});
 5034}
 5035
 5036#[gpui::test]
 5037async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5038    init_test(cx, |settings| {
 5039        settings.defaults.tab_size = NonZeroU32::new(3)
 5040    });
 5041
 5042    let mut cx = EditorTestContext::new(cx).await;
 5043
 5044    // MULTI SELECTION
 5045    // Ln.1 "«" tests empty lines
 5046    // Ln.9 tests just leading whitespace
 5047    cx.set_state(indoc! {"
 5048        «
 5049        abc                 // No indentationˇ»
 5050        «\tabc              // 1 tabˇ»
 5051        \t\tabc «      ˇ»   // 2 tabs
 5052        \t ab«c             // Tab followed by space
 5053         \tabc              // Space followed by tab (3 spaces should be the result)
 5054        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5055           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5056        \t
 5057        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5058    "});
 5059    cx.update_editor(|e, window, cx| {
 5060        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5061    });
 5062    cx.assert_editor_state(
 5063        indoc! {"
 5064            «
 5065            abc                 // No indentation
 5066               abc              // 1 tab
 5067                  abc          // 2 tabs
 5068                abc             // Tab followed by space
 5069               abc              // Space followed by tab (3 spaces should be the result)
 5070                           abc   // Mixed indentation (tab conversion depends on the column)
 5071               abc         // Already space indented
 5072               ·
 5073               abc\tdef          // Only the leading tab is manipulatedˇ»
 5074        "}
 5075        .replace("·", "")
 5076        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5077    );
 5078
 5079    // Test on just a few lines, the others should remain unchanged
 5080    // Only lines (3, 5, 10, 11) should change
 5081    cx.set_state(
 5082        indoc! {"
 5083            ·
 5084            abc                 // No indentation
 5085            \tabcˇ               // 1 tab
 5086            \t\tabc             // 2 tabs
 5087            \t abcˇ              // Tab followed by space
 5088             \tabc              // Space followed by tab (3 spaces should be the result)
 5089            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5090               abc              // Already space indented
 5091            «\t
 5092            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5093        "}
 5094        .replace("·", "")
 5095        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5096    );
 5097    cx.update_editor(|e, window, cx| {
 5098        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5099    });
 5100    cx.assert_editor_state(
 5101        indoc! {"
 5102            ·
 5103            abc                 // No indentation
 5104            «   abc               // 1 tabˇ»
 5105            \t\tabc             // 2 tabs
 5106            «    abc              // Tab followed by spaceˇ»
 5107             \tabc              // Space followed by tab (3 spaces should be the result)
 5108            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5109               abc              // Already space indented
 5110            «   ·
 5111               abc\tdef          // Only the leading tab is manipulatedˇ»
 5112        "}
 5113        .replace("·", "")
 5114        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5115    );
 5116
 5117    // SINGLE SELECTION
 5118    // Ln.1 "«" tests empty lines
 5119    // Ln.9 tests just leading whitespace
 5120    cx.set_state(indoc! {"
 5121        «
 5122        abc                 // No indentation
 5123        \tabc               // 1 tab
 5124        \t\tabc             // 2 tabs
 5125        \t abc              // Tab followed by space
 5126         \tabc              // Space followed by tab (3 spaces should be the result)
 5127        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5128           abc              // Already space indented
 5129        \t
 5130        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5131    "});
 5132    cx.update_editor(|e, window, cx| {
 5133        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5134    });
 5135    cx.assert_editor_state(
 5136        indoc! {"
 5137            «
 5138            abc                 // No indentation
 5139               abc               // 1 tab
 5140                  abc             // 2 tabs
 5141                abc              // Tab followed by space
 5142               abc              // Space followed by tab (3 spaces should be the result)
 5143                           abc   // Mixed indentation (tab conversion depends on the column)
 5144               abc              // Already space indented
 5145               ·
 5146               abc\tdef          // Only the leading tab is manipulatedˇ»
 5147        "}
 5148        .replace("·", "")
 5149        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5150    );
 5151}
 5152
 5153#[gpui::test]
 5154async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5155    init_test(cx, |settings| {
 5156        settings.defaults.tab_size = NonZeroU32::new(3)
 5157    });
 5158
 5159    let mut cx = EditorTestContext::new(cx).await;
 5160
 5161    // MULTI SELECTION
 5162    // Ln.1 "«" tests empty lines
 5163    // Ln.11 tests just leading whitespace
 5164    cx.set_state(indoc! {"
 5165        «
 5166        abˇ»ˇc                 // No indentation
 5167         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5168          abc  «             // 2 spaces (< 3 so dont convert)
 5169           abc              // 3 spaces (convert)
 5170             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5171        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5172        «\t abc              // Tab followed by space
 5173         \tabc              // Space followed by tab (should be consumed due to tab)
 5174        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5175           \tˇ»  «\t
 5176           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5177    "});
 5178    cx.update_editor(|e, window, cx| {
 5179        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5180    });
 5181    cx.assert_editor_state(indoc! {"
 5182        «
 5183        abc                 // No indentation
 5184         abc                // 1 space (< 3 so dont convert)
 5185          abc               // 2 spaces (< 3 so dont convert)
 5186        \tabc              // 3 spaces (convert)
 5187        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5188        \t\t\tabc           // Already tab indented
 5189        \t abc              // Tab followed by space
 5190        \tabc              // Space followed by tab (should be consumed due to tab)
 5191        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5192        \t\t\t
 5193        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5194    "});
 5195
 5196    // Test on just a few lines, the other should remain unchanged
 5197    // Only lines (4, 8, 11, 12) should change
 5198    cx.set_state(
 5199        indoc! {"
 5200            ·
 5201            abc                 // No indentation
 5202             abc                // 1 space (< 3 so dont convert)
 5203              abc               // 2 spaces (< 3 so dont convert)
 5204            «   abc              // 3 spaces (convert)ˇ»
 5205                 abc            // 5 spaces (1 tab + 2 spaces)
 5206            \t\t\tabc           // Already tab indented
 5207            \t abc              // Tab followed by space
 5208             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5209               \t\t  \tabc      // Mixed indentation
 5210            \t \t  \t   \tabc   // Mixed indentation
 5211               \t  \tˇ
 5212            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5213        "}
 5214        .replace("·", "")
 5215        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5216    );
 5217    cx.update_editor(|e, window, cx| {
 5218        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5219    });
 5220    cx.assert_editor_state(
 5221        indoc! {"
 5222            ·
 5223            abc                 // No indentation
 5224             abc                // 1 space (< 3 so dont convert)
 5225              abc               // 2 spaces (< 3 so dont convert)
 5226            «\tabc              // 3 spaces (convert)ˇ»
 5227                 abc            // 5 spaces (1 tab + 2 spaces)
 5228            \t\t\tabc           // Already tab indented
 5229            \t abc              // Tab followed by space
 5230            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5231               \t\t  \tabc      // Mixed indentation
 5232            \t \t  \t   \tabc   // Mixed indentation
 5233            «\t\t\t
 5234            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5235        "}
 5236        .replace("·", "")
 5237        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5238    );
 5239
 5240    // SINGLE SELECTION
 5241    // Ln.1 "«" tests empty lines
 5242    // Ln.11 tests just leading whitespace
 5243    cx.set_state(indoc! {"
 5244        «
 5245        abc                 // No indentation
 5246         abc                // 1 space (< 3 so dont convert)
 5247          abc               // 2 spaces (< 3 so dont convert)
 5248           abc              // 3 spaces (convert)
 5249             abc            // 5 spaces (1 tab + 2 spaces)
 5250        \t\t\tabc           // Already tab indented
 5251        \t abc              // Tab followed by space
 5252         \tabc              // Space followed by tab (should be consumed due to tab)
 5253        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5254           \t  \t
 5255           abc   \t         // Only the leading spaces should be convertedˇ»
 5256    "});
 5257    cx.update_editor(|e, window, cx| {
 5258        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5259    });
 5260    cx.assert_editor_state(indoc! {"
 5261        «
 5262        abc                 // No indentation
 5263         abc                // 1 space (< 3 so dont convert)
 5264          abc               // 2 spaces (< 3 so dont convert)
 5265        \tabc              // 3 spaces (convert)
 5266        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5267        \t\t\tabc           // Already tab indented
 5268        \t abc              // Tab followed by space
 5269        \tabc              // Space followed by tab (should be consumed due to tab)
 5270        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5271        \t\t\t
 5272        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5273    "});
 5274}
 5275
 5276#[gpui::test]
 5277async fn test_toggle_case(cx: &mut TestAppContext) {
 5278    init_test(cx, |_| {});
 5279
 5280    let mut cx = EditorTestContext::new(cx).await;
 5281
 5282    // If all lower case -> upper case
 5283    cx.set_state(indoc! {"
 5284        «hello worldˇ»
 5285    "});
 5286    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5287    cx.assert_editor_state(indoc! {"
 5288        «HELLO WORLDˇ»
 5289    "});
 5290
 5291    // If all upper case -> lower case
 5292    cx.set_state(indoc! {"
 5293        «HELLO WORLDˇ»
 5294    "});
 5295    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5296    cx.assert_editor_state(indoc! {"
 5297        «hello worldˇ»
 5298    "});
 5299
 5300    // If any upper case characters are identified -> lower case
 5301    // This matches JetBrains IDEs
 5302    cx.set_state(indoc! {"
 5303        «hEllo worldˇ»
 5304    "});
 5305    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5306    cx.assert_editor_state(indoc! {"
 5307        «hello worldˇ»
 5308    "});
 5309}
 5310
 5311#[gpui::test]
 5312async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5313    init_test(cx, |_| {});
 5314
 5315    let mut cx = EditorTestContext::new(cx).await;
 5316
 5317    cx.set_state(indoc! {"
 5318        «implement-windows-supportˇ»
 5319    "});
 5320    cx.update_editor(|e, window, cx| {
 5321        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5322    });
 5323    cx.assert_editor_state(indoc! {"
 5324        «Implement windows supportˇ»
 5325    "});
 5326}
 5327
 5328#[gpui::test]
 5329async fn test_manipulate_text(cx: &mut TestAppContext) {
 5330    init_test(cx, |_| {});
 5331
 5332    let mut cx = EditorTestContext::new(cx).await;
 5333
 5334    // Test convert_to_upper_case()
 5335    cx.set_state(indoc! {"
 5336        «hello worldˇ»
 5337    "});
 5338    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5339    cx.assert_editor_state(indoc! {"
 5340        «HELLO WORLDˇ»
 5341    "});
 5342
 5343    // Test convert_to_lower_case()
 5344    cx.set_state(indoc! {"
 5345        «HELLO WORLDˇ»
 5346    "});
 5347    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5348    cx.assert_editor_state(indoc! {"
 5349        «hello worldˇ»
 5350    "});
 5351
 5352    // Test multiple line, single selection case
 5353    cx.set_state(indoc! {"
 5354        «The quick brown
 5355        fox jumps over
 5356        the lazy dogˇ»
 5357    "});
 5358    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5359    cx.assert_editor_state(indoc! {"
 5360        «The Quick Brown
 5361        Fox Jumps Over
 5362        The Lazy Dogˇ»
 5363    "});
 5364
 5365    // Test multiple line, single selection case
 5366    cx.set_state(indoc! {"
 5367        «The quick brown
 5368        fox jumps over
 5369        the lazy dogˇ»
 5370    "});
 5371    cx.update_editor(|e, window, cx| {
 5372        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5373    });
 5374    cx.assert_editor_state(indoc! {"
 5375        «TheQuickBrown
 5376        FoxJumpsOver
 5377        TheLazyDogˇ»
 5378    "});
 5379
 5380    // From here on out, test more complex cases of manipulate_text()
 5381
 5382    // Test no selection case - should affect words cursors are in
 5383    // Cursor at beginning, middle, and end of word
 5384    cx.set_state(indoc! {"
 5385        ˇhello big beauˇtiful worldˇ
 5386    "});
 5387    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5388    cx.assert_editor_state(indoc! {"
 5389        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5390    "});
 5391
 5392    // Test multiple selections on a single line and across multiple lines
 5393    cx.set_state(indoc! {"
 5394        «Theˇ» quick «brown
 5395        foxˇ» jumps «overˇ»
 5396        the «lazyˇ» dog
 5397    "});
 5398    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5399    cx.assert_editor_state(indoc! {"
 5400        «THEˇ» quick «BROWN
 5401        FOXˇ» jumps «OVERˇ»
 5402        the «LAZYˇ» dog
 5403    "});
 5404
 5405    // Test case where text length grows
 5406    cx.set_state(indoc! {"
 5407        «tschüߡ»
 5408    "});
 5409    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5410    cx.assert_editor_state(indoc! {"
 5411        «TSCHÜSSˇ»
 5412    "});
 5413
 5414    // Test to make sure we don't crash when text shrinks
 5415    cx.set_state(indoc! {"
 5416        aaa_bbbˇ
 5417    "});
 5418    cx.update_editor(|e, window, cx| {
 5419        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5420    });
 5421    cx.assert_editor_state(indoc! {"
 5422        «aaaBbbˇ»
 5423    "});
 5424
 5425    // Test to make sure we all aware of the fact that each word can grow and shrink
 5426    // Final selections should be aware of this fact
 5427    cx.set_state(indoc! {"
 5428        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5429    "});
 5430    cx.update_editor(|e, window, cx| {
 5431        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5432    });
 5433    cx.assert_editor_state(indoc! {"
 5434        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5435    "});
 5436
 5437    cx.set_state(indoc! {"
 5438        «hElLo, WoRld!ˇ»
 5439    "});
 5440    cx.update_editor(|e, window, cx| {
 5441        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5442    });
 5443    cx.assert_editor_state(indoc! {"
 5444        «HeLlO, wOrLD!ˇ»
 5445    "});
 5446
 5447    // Test selections with `line_mode() = true`.
 5448    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5449    cx.set_state(indoc! {"
 5450        «The quick brown
 5451        fox jumps over
 5452        tˇ»he lazy dog
 5453    "});
 5454    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5455    cx.assert_editor_state(indoc! {"
 5456        «THE QUICK BROWN
 5457        FOX JUMPS OVER
 5458        THE LAZY DOGˇ»
 5459    "});
 5460}
 5461
 5462#[gpui::test]
 5463fn test_duplicate_line(cx: &mut TestAppContext) {
 5464    init_test(cx, |_| {});
 5465
 5466    let editor = cx.add_window(|window, cx| {
 5467        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5468        build_editor(buffer, window, cx)
 5469    });
 5470    _ = editor.update(cx, |editor, window, cx| {
 5471        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5472            s.select_display_ranges([
 5473                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5474                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5475                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5476                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5477            ])
 5478        });
 5479        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5480        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5481        assert_eq!(
 5482            editor.selections.display_ranges(cx),
 5483            vec![
 5484                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5485                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5486                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5487                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5488            ]
 5489        );
 5490    });
 5491
 5492    let editor = cx.add_window(|window, cx| {
 5493        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5494        build_editor(buffer, window, cx)
 5495    });
 5496    _ = editor.update(cx, |editor, window, cx| {
 5497        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5498            s.select_display_ranges([
 5499                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5500                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5501            ])
 5502        });
 5503        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5504        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5505        assert_eq!(
 5506            editor.selections.display_ranges(cx),
 5507            vec![
 5508                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5509                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5510            ]
 5511        );
 5512    });
 5513
 5514    // With `move_upwards` the selections stay in place, except for
 5515    // the lines inserted above them
 5516    let editor = cx.add_window(|window, cx| {
 5517        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5518        build_editor(buffer, window, cx)
 5519    });
 5520    _ = editor.update(cx, |editor, window, cx| {
 5521        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5522            s.select_display_ranges([
 5523                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5524                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5525                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5526                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5527            ])
 5528        });
 5529        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5530        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5531        assert_eq!(
 5532            editor.selections.display_ranges(cx),
 5533            vec![
 5534                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5535                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5536                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5537                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5538            ]
 5539        );
 5540    });
 5541
 5542    let editor = cx.add_window(|window, cx| {
 5543        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5544        build_editor(buffer, window, cx)
 5545    });
 5546    _ = editor.update(cx, |editor, window, cx| {
 5547        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5548            s.select_display_ranges([
 5549                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5550                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5551            ])
 5552        });
 5553        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5554        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5555        assert_eq!(
 5556            editor.selections.display_ranges(cx),
 5557            vec![
 5558                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5559                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5560            ]
 5561        );
 5562    });
 5563
 5564    let editor = cx.add_window(|window, cx| {
 5565        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5566        build_editor(buffer, window, cx)
 5567    });
 5568    _ = editor.update(cx, |editor, window, cx| {
 5569        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5570            s.select_display_ranges([
 5571                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5572                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5573            ])
 5574        });
 5575        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5576        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5577        assert_eq!(
 5578            editor.selections.display_ranges(cx),
 5579            vec![
 5580                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5581                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5582            ]
 5583        );
 5584    });
 5585}
 5586
 5587#[gpui::test]
 5588fn test_move_line_up_down(cx: &mut TestAppContext) {
 5589    init_test(cx, |_| {});
 5590
 5591    let editor = cx.add_window(|window, cx| {
 5592        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5593        build_editor(buffer, window, cx)
 5594    });
 5595    _ = editor.update(cx, |editor, window, cx| {
 5596        editor.fold_creases(
 5597            vec![
 5598                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5599                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5600                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5601            ],
 5602            true,
 5603            window,
 5604            cx,
 5605        );
 5606        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5607            s.select_display_ranges([
 5608                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5609                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5610                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5611                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5612            ])
 5613        });
 5614        assert_eq!(
 5615            editor.display_text(cx),
 5616            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5617        );
 5618
 5619        editor.move_line_up(&MoveLineUp, window, cx);
 5620        assert_eq!(
 5621            editor.display_text(cx),
 5622            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5623        );
 5624        assert_eq!(
 5625            editor.selections.display_ranges(cx),
 5626            vec![
 5627                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5628                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5629                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5630                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5631            ]
 5632        );
 5633    });
 5634
 5635    _ = editor.update(cx, |editor, window, cx| {
 5636        editor.move_line_down(&MoveLineDown, window, cx);
 5637        assert_eq!(
 5638            editor.display_text(cx),
 5639            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5640        );
 5641        assert_eq!(
 5642            editor.selections.display_ranges(cx),
 5643            vec![
 5644                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5645                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5646                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5647                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5648            ]
 5649        );
 5650    });
 5651
 5652    _ = editor.update(cx, |editor, window, cx| {
 5653        editor.move_line_down(&MoveLineDown, window, cx);
 5654        assert_eq!(
 5655            editor.display_text(cx),
 5656            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5657        );
 5658        assert_eq!(
 5659            editor.selections.display_ranges(cx),
 5660            vec![
 5661                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5662                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5663                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5664                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5665            ]
 5666        );
 5667    });
 5668
 5669    _ = editor.update(cx, |editor, window, cx| {
 5670        editor.move_line_up(&MoveLineUp, window, cx);
 5671        assert_eq!(
 5672            editor.display_text(cx),
 5673            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5674        );
 5675        assert_eq!(
 5676            editor.selections.display_ranges(cx),
 5677            vec![
 5678                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5679                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5680                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5681                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5682            ]
 5683        );
 5684    });
 5685}
 5686
 5687#[gpui::test]
 5688fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5689    init_test(cx, |_| {});
 5690    let editor = cx.add_window(|window, cx| {
 5691        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5692        build_editor(buffer, window, cx)
 5693    });
 5694    _ = editor.update(cx, |editor, window, cx| {
 5695        editor.fold_creases(
 5696            vec![Crease::simple(
 5697                Point::new(6, 4)..Point::new(7, 4),
 5698                FoldPlaceholder::test(),
 5699            )],
 5700            true,
 5701            window,
 5702            cx,
 5703        );
 5704        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5705            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5706        });
 5707        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5708        editor.move_line_up(&MoveLineUp, window, cx);
 5709        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5710        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5711    });
 5712}
 5713
 5714#[gpui::test]
 5715fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5716    init_test(cx, |_| {});
 5717
 5718    let editor = cx.add_window(|window, cx| {
 5719        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5720        build_editor(buffer, window, cx)
 5721    });
 5722    _ = editor.update(cx, |editor, window, cx| {
 5723        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5724        editor.insert_blocks(
 5725            [BlockProperties {
 5726                style: BlockStyle::Fixed,
 5727                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5728                height: Some(1),
 5729                render: Arc::new(|_| div().into_any()),
 5730                priority: 0,
 5731            }],
 5732            Some(Autoscroll::fit()),
 5733            cx,
 5734        );
 5735        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5736            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5737        });
 5738        editor.move_line_down(&MoveLineDown, window, cx);
 5739    });
 5740}
 5741
 5742#[gpui::test]
 5743async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5744    init_test(cx, |_| {});
 5745
 5746    let mut cx = EditorTestContext::new(cx).await;
 5747    cx.set_state(
 5748        &"
 5749            ˇzero
 5750            one
 5751            two
 5752            three
 5753            four
 5754            five
 5755        "
 5756        .unindent(),
 5757    );
 5758
 5759    // Create a four-line block that replaces three lines of text.
 5760    cx.update_editor(|editor, window, cx| {
 5761        let snapshot = editor.snapshot(window, cx);
 5762        let snapshot = &snapshot.buffer_snapshot();
 5763        let placement = BlockPlacement::Replace(
 5764            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5765        );
 5766        editor.insert_blocks(
 5767            [BlockProperties {
 5768                placement,
 5769                height: Some(4),
 5770                style: BlockStyle::Sticky,
 5771                render: Arc::new(|_| gpui::div().into_any_element()),
 5772                priority: 0,
 5773            }],
 5774            None,
 5775            cx,
 5776        );
 5777    });
 5778
 5779    // Move down so that the cursor touches the block.
 5780    cx.update_editor(|editor, window, cx| {
 5781        editor.move_down(&Default::default(), window, cx);
 5782    });
 5783    cx.assert_editor_state(
 5784        &"
 5785            zero
 5786            «one
 5787            two
 5788            threeˇ»
 5789            four
 5790            five
 5791        "
 5792        .unindent(),
 5793    );
 5794
 5795    // Move down past the block.
 5796    cx.update_editor(|editor, window, cx| {
 5797        editor.move_down(&Default::default(), window, cx);
 5798    });
 5799    cx.assert_editor_state(
 5800        &"
 5801            zero
 5802            one
 5803            two
 5804            three
 5805            ˇfour
 5806            five
 5807        "
 5808        .unindent(),
 5809    );
 5810}
 5811
 5812#[gpui::test]
 5813fn test_transpose(cx: &mut TestAppContext) {
 5814    init_test(cx, |_| {});
 5815
 5816    _ = cx.add_window(|window, cx| {
 5817        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5818        editor.set_style(EditorStyle::default(), window, cx);
 5819        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5820            s.select_ranges([1..1])
 5821        });
 5822        editor.transpose(&Default::default(), window, cx);
 5823        assert_eq!(editor.text(cx), "bac");
 5824        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5825
 5826        editor.transpose(&Default::default(), window, cx);
 5827        assert_eq!(editor.text(cx), "bca");
 5828        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5829
 5830        editor.transpose(&Default::default(), window, cx);
 5831        assert_eq!(editor.text(cx), "bac");
 5832        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5833
 5834        editor
 5835    });
 5836
 5837    _ = cx.add_window(|window, cx| {
 5838        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5839        editor.set_style(EditorStyle::default(), window, cx);
 5840        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5841            s.select_ranges([3..3])
 5842        });
 5843        editor.transpose(&Default::default(), window, cx);
 5844        assert_eq!(editor.text(cx), "acb\nde");
 5845        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5846
 5847        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5848            s.select_ranges([4..4])
 5849        });
 5850        editor.transpose(&Default::default(), window, cx);
 5851        assert_eq!(editor.text(cx), "acbd\ne");
 5852        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5853
 5854        editor.transpose(&Default::default(), window, cx);
 5855        assert_eq!(editor.text(cx), "acbde\n");
 5856        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5857
 5858        editor.transpose(&Default::default(), window, cx);
 5859        assert_eq!(editor.text(cx), "acbd\ne");
 5860        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5861
 5862        editor
 5863    });
 5864
 5865    _ = cx.add_window(|window, cx| {
 5866        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5867        editor.set_style(EditorStyle::default(), window, cx);
 5868        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5869            s.select_ranges([1..1, 2..2, 4..4])
 5870        });
 5871        editor.transpose(&Default::default(), window, cx);
 5872        assert_eq!(editor.text(cx), "bacd\ne");
 5873        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5874
 5875        editor.transpose(&Default::default(), window, cx);
 5876        assert_eq!(editor.text(cx), "bcade\n");
 5877        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5878
 5879        editor.transpose(&Default::default(), window, cx);
 5880        assert_eq!(editor.text(cx), "bcda\ne");
 5881        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5882
 5883        editor.transpose(&Default::default(), window, cx);
 5884        assert_eq!(editor.text(cx), "bcade\n");
 5885        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5886
 5887        editor.transpose(&Default::default(), window, cx);
 5888        assert_eq!(editor.text(cx), "bcaed\n");
 5889        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5890
 5891        editor
 5892    });
 5893
 5894    _ = cx.add_window(|window, cx| {
 5895        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5896        editor.set_style(EditorStyle::default(), window, cx);
 5897        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5898            s.select_ranges([4..4])
 5899        });
 5900        editor.transpose(&Default::default(), window, cx);
 5901        assert_eq!(editor.text(cx), "🏀🍐✋");
 5902        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5903
 5904        editor.transpose(&Default::default(), window, cx);
 5905        assert_eq!(editor.text(cx), "🏀✋🍐");
 5906        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5907
 5908        editor.transpose(&Default::default(), window, cx);
 5909        assert_eq!(editor.text(cx), "🏀🍐✋");
 5910        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5911
 5912        editor
 5913    });
 5914}
 5915
 5916#[gpui::test]
 5917async fn test_rewrap(cx: &mut TestAppContext) {
 5918    init_test(cx, |settings| {
 5919        settings.languages.0.extend([
 5920            (
 5921                "Markdown".into(),
 5922                LanguageSettingsContent {
 5923                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5924                    preferred_line_length: Some(40),
 5925                    ..Default::default()
 5926                },
 5927            ),
 5928            (
 5929                "Plain Text".into(),
 5930                LanguageSettingsContent {
 5931                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5932                    preferred_line_length: Some(40),
 5933                    ..Default::default()
 5934                },
 5935            ),
 5936            (
 5937                "C++".into(),
 5938                LanguageSettingsContent {
 5939                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5940                    preferred_line_length: Some(40),
 5941                    ..Default::default()
 5942                },
 5943            ),
 5944            (
 5945                "Python".into(),
 5946                LanguageSettingsContent {
 5947                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5948                    preferred_line_length: Some(40),
 5949                    ..Default::default()
 5950                },
 5951            ),
 5952            (
 5953                "Rust".into(),
 5954                LanguageSettingsContent {
 5955                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5956                    preferred_line_length: Some(40),
 5957                    ..Default::default()
 5958                },
 5959            ),
 5960        ])
 5961    });
 5962
 5963    let mut cx = EditorTestContext::new(cx).await;
 5964
 5965    let cpp_language = Arc::new(Language::new(
 5966        LanguageConfig {
 5967            name: "C++".into(),
 5968            line_comments: vec!["// ".into()],
 5969            ..LanguageConfig::default()
 5970        },
 5971        None,
 5972    ));
 5973    let python_language = Arc::new(Language::new(
 5974        LanguageConfig {
 5975            name: "Python".into(),
 5976            line_comments: vec!["# ".into()],
 5977            ..LanguageConfig::default()
 5978        },
 5979        None,
 5980    ));
 5981    let markdown_language = Arc::new(Language::new(
 5982        LanguageConfig {
 5983            name: "Markdown".into(),
 5984            rewrap_prefixes: vec![
 5985                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5986                regex::Regex::new("[-*+]\\s+").unwrap(),
 5987            ],
 5988            ..LanguageConfig::default()
 5989        },
 5990        None,
 5991    ));
 5992    let rust_language = Arc::new(
 5993        Language::new(
 5994            LanguageConfig {
 5995                name: "Rust".into(),
 5996                line_comments: vec!["// ".into(), "/// ".into()],
 5997                ..LanguageConfig::default()
 5998            },
 5999            Some(tree_sitter_rust::LANGUAGE.into()),
 6000        )
 6001        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6002        .unwrap(),
 6003    );
 6004
 6005    let plaintext_language = Arc::new(Language::new(
 6006        LanguageConfig {
 6007            name: "Plain Text".into(),
 6008            ..LanguageConfig::default()
 6009        },
 6010        None,
 6011    ));
 6012
 6013    // Test basic rewrapping of a long line with a cursor
 6014    assert_rewrap(
 6015        indoc! {"
 6016            // ˇThis is a long comment that needs to be wrapped.
 6017        "},
 6018        indoc! {"
 6019            // ˇThis is a long comment that needs to
 6020            // be wrapped.
 6021        "},
 6022        cpp_language.clone(),
 6023        &mut cx,
 6024    );
 6025
 6026    // Test rewrapping a full selection
 6027    assert_rewrap(
 6028        indoc! {"
 6029            «// This selected long comment needs to be wrapped.ˇ»"
 6030        },
 6031        indoc! {"
 6032            «// This selected long comment needs to
 6033            // be wrapped.ˇ»"
 6034        },
 6035        cpp_language.clone(),
 6036        &mut cx,
 6037    );
 6038
 6039    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6040    assert_rewrap(
 6041        indoc! {"
 6042            // ˇThis is the first line.
 6043            // Thisˇ is the second line.
 6044            // This is the thirdˇ line, all part of one paragraph.
 6045         "},
 6046        indoc! {"
 6047            // ˇThis is the first line. Thisˇ is the
 6048            // second line. This is the thirdˇ line,
 6049            // all part of one paragraph.
 6050         "},
 6051        cpp_language.clone(),
 6052        &mut cx,
 6053    );
 6054
 6055    // Test multiple cursors in different paragraphs trigger separate rewraps
 6056    assert_rewrap(
 6057        indoc! {"
 6058            // ˇThis is the first paragraph, first line.
 6059            // ˇThis is the first paragraph, second line.
 6060
 6061            // ˇThis is the second paragraph, first line.
 6062            // ˇThis is the second paragraph, second line.
 6063        "},
 6064        indoc! {"
 6065            // ˇThis is the first paragraph, first
 6066            // line. ˇThis is the first paragraph,
 6067            // second line.
 6068
 6069            // ˇThis is the second paragraph, first
 6070            // line. ˇThis is the second paragraph,
 6071            // second line.
 6072        "},
 6073        cpp_language.clone(),
 6074        &mut cx,
 6075    );
 6076
 6077    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6078    assert_rewrap(
 6079        indoc! {"
 6080            «// A regular long long comment to be wrapped.
 6081            /// A documentation long comment to be wrapped.ˇ»
 6082          "},
 6083        indoc! {"
 6084            «// A regular long long comment to be
 6085            // wrapped.
 6086            /// A documentation long comment to be
 6087            /// wrapped.ˇ»
 6088          "},
 6089        rust_language.clone(),
 6090        &mut cx,
 6091    );
 6092
 6093    // Test that change in indentation level trigger seperate rewraps
 6094    assert_rewrap(
 6095        indoc! {"
 6096            fn foo() {
 6097                «// This is a long comment at the base indent.
 6098                    // This is a long comment at the next indent.ˇ»
 6099            }
 6100        "},
 6101        indoc! {"
 6102            fn foo() {
 6103                «// This is a long comment at the
 6104                // base indent.
 6105                    // This is a long comment at the
 6106                    // next indent.ˇ»
 6107            }
 6108        "},
 6109        rust_language.clone(),
 6110        &mut cx,
 6111    );
 6112
 6113    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6114    assert_rewrap(
 6115        indoc! {"
 6116            # ˇThis is a long comment using a pound sign.
 6117        "},
 6118        indoc! {"
 6119            # ˇThis is a long comment using a pound
 6120            # sign.
 6121        "},
 6122        python_language,
 6123        &mut cx,
 6124    );
 6125
 6126    // Test rewrapping only affects comments, not code even when selected
 6127    assert_rewrap(
 6128        indoc! {"
 6129            «/// This doc comment is long and should be wrapped.
 6130            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6131        "},
 6132        indoc! {"
 6133            «/// This doc comment is long and should
 6134            /// be wrapped.
 6135            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6136        "},
 6137        rust_language.clone(),
 6138        &mut cx,
 6139    );
 6140
 6141    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6142    assert_rewrap(
 6143        indoc! {"
 6144            # Header
 6145
 6146            A long long long line of markdown text to wrap.ˇ
 6147         "},
 6148        indoc! {"
 6149            # Header
 6150
 6151            A long long long line of markdown text
 6152            to wrap.ˇ
 6153         "},
 6154        markdown_language.clone(),
 6155        &mut cx,
 6156    );
 6157
 6158    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6159    assert_rewrap(
 6160        indoc! {"
 6161            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6162            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6163            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6164        "},
 6165        indoc! {"
 6166            «1. This is a numbered list item that is
 6167               very long and needs to be wrapped
 6168               properly.
 6169            2. This is a numbered list item that is
 6170               very long and needs to be wrapped
 6171               properly.
 6172            - This is an unordered list item that is
 6173              also very long and should not merge
 6174              with the numbered item.ˇ»
 6175        "},
 6176        markdown_language.clone(),
 6177        &mut cx,
 6178    );
 6179
 6180    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6181    assert_rewrap(
 6182        indoc! {"
 6183            «1. This is a numbered list item that is
 6184            very long and needs to be wrapped
 6185            properly.
 6186            2. This is a numbered list item that is
 6187            very long and needs to be wrapped
 6188            properly.
 6189            - This is an unordered list item that is
 6190            also very long and should not merge with
 6191            the numbered item.ˇ»
 6192        "},
 6193        indoc! {"
 6194            «1. This is a numbered list item that is
 6195               very long and needs to be wrapped
 6196               properly.
 6197            2. This is a numbered list item that is
 6198               very long and needs to be wrapped
 6199               properly.
 6200            - This is an unordered list item that is
 6201              also very long and should not merge
 6202              with the numbered item.ˇ»
 6203        "},
 6204        markdown_language.clone(),
 6205        &mut cx,
 6206    );
 6207
 6208    // Test that rewrapping maintain indents even when they already exists.
 6209    assert_rewrap(
 6210        indoc! {"
 6211            «1. This is a numbered list
 6212               item that is very long and needs to be wrapped properly.
 6213            2. This is a numbered list
 6214               item that is very long and needs to be wrapped properly.
 6215            - This is an unordered list item that is also very long and
 6216              should not merge with the numbered item.ˇ»
 6217        "},
 6218        indoc! {"
 6219            «1. This is a numbered list item that is
 6220               very long and needs to be wrapped
 6221               properly.
 6222            2. This is a numbered list item that is
 6223               very long and needs to be wrapped
 6224               properly.
 6225            - This is an unordered list item that is
 6226              also very long and should not merge
 6227              with the numbered item.ˇ»
 6228        "},
 6229        markdown_language,
 6230        &mut cx,
 6231    );
 6232
 6233    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6234    assert_rewrap(
 6235        indoc! {"
 6236            ˇThis is a very long line of plain text that will be wrapped.
 6237        "},
 6238        indoc! {"
 6239            ˇThis is a very long line of plain text
 6240            that will be wrapped.
 6241        "},
 6242        plaintext_language.clone(),
 6243        &mut cx,
 6244    );
 6245
 6246    // Test that non-commented code acts as a paragraph boundary within a selection
 6247    assert_rewrap(
 6248        indoc! {"
 6249               «// This is the first long comment block to be wrapped.
 6250               fn my_func(a: u32);
 6251               // This is the second long comment block to be wrapped.ˇ»
 6252           "},
 6253        indoc! {"
 6254               «// This is the first long comment block
 6255               // to be wrapped.
 6256               fn my_func(a: u32);
 6257               // This is the second long comment block
 6258               // to be wrapped.ˇ»
 6259           "},
 6260        rust_language,
 6261        &mut cx,
 6262    );
 6263
 6264    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6265    assert_rewrap(
 6266        indoc! {"
 6267            «ˇThis is a very long line that will be wrapped.
 6268
 6269            This is another paragraph in the same selection.»
 6270
 6271            «\tThis is a very long indented line that will be wrapped.ˇ»
 6272         "},
 6273        indoc! {"
 6274            «ˇThis is a very long line that will be
 6275            wrapped.
 6276
 6277            This is another paragraph in the same
 6278            selection.»
 6279
 6280            «\tThis is a very long indented line
 6281            \tthat will be wrapped.ˇ»
 6282         "},
 6283        plaintext_language,
 6284        &mut cx,
 6285    );
 6286
 6287    // Test that an empty comment line acts as a paragraph boundary
 6288    assert_rewrap(
 6289        indoc! {"
 6290            // ˇThis is a long comment that will be wrapped.
 6291            //
 6292            // And this is another long comment that will also be wrapped.ˇ
 6293         "},
 6294        indoc! {"
 6295            // ˇThis is a long comment that will be
 6296            // wrapped.
 6297            //
 6298            // And this is another long comment that
 6299            // will also be wrapped.ˇ
 6300         "},
 6301        cpp_language,
 6302        &mut cx,
 6303    );
 6304
 6305    #[track_caller]
 6306    fn assert_rewrap(
 6307        unwrapped_text: &str,
 6308        wrapped_text: &str,
 6309        language: Arc<Language>,
 6310        cx: &mut EditorTestContext,
 6311    ) {
 6312        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6313        cx.set_state(unwrapped_text);
 6314        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6315        cx.assert_editor_state(wrapped_text);
 6316    }
 6317}
 6318
 6319#[gpui::test]
 6320async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6321    init_test(cx, |settings| {
 6322        settings.languages.0.extend([(
 6323            "Rust".into(),
 6324            LanguageSettingsContent {
 6325                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6326                preferred_line_length: Some(40),
 6327                ..Default::default()
 6328            },
 6329        )])
 6330    });
 6331
 6332    let mut cx = EditorTestContext::new(cx).await;
 6333
 6334    let rust_lang = Arc::new(
 6335        Language::new(
 6336            LanguageConfig {
 6337                name: "Rust".into(),
 6338                line_comments: vec!["// ".into()],
 6339                block_comment: Some(BlockCommentConfig {
 6340                    start: "/*".into(),
 6341                    end: "*/".into(),
 6342                    prefix: "* ".into(),
 6343                    tab_size: 1,
 6344                }),
 6345                documentation_comment: Some(BlockCommentConfig {
 6346                    start: "/**".into(),
 6347                    end: "*/".into(),
 6348                    prefix: "* ".into(),
 6349                    tab_size: 1,
 6350                }),
 6351
 6352                ..LanguageConfig::default()
 6353            },
 6354            Some(tree_sitter_rust::LANGUAGE.into()),
 6355        )
 6356        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6357        .unwrap(),
 6358    );
 6359
 6360    // regular block comment
 6361    assert_rewrap(
 6362        indoc! {"
 6363            /*
 6364             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6365             */
 6366            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6367        "},
 6368        indoc! {"
 6369            /*
 6370             *ˇ Lorem ipsum dolor sit amet,
 6371             * consectetur adipiscing elit.
 6372             */
 6373            /*
 6374             *ˇ Lorem ipsum dolor sit amet,
 6375             * consectetur adipiscing elit.
 6376             */
 6377        "},
 6378        rust_lang.clone(),
 6379        &mut cx,
 6380    );
 6381
 6382    // indent is respected
 6383    assert_rewrap(
 6384        indoc! {"
 6385            {}
 6386                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6387        "},
 6388        indoc! {"
 6389            {}
 6390                /*
 6391                 *ˇ Lorem ipsum dolor sit amet,
 6392                 * consectetur adipiscing elit.
 6393                 */
 6394        "},
 6395        rust_lang.clone(),
 6396        &mut cx,
 6397    );
 6398
 6399    // short block comments with inline delimiters
 6400    assert_rewrap(
 6401        indoc! {"
 6402            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6403            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6404             */
 6405            /*
 6406             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6407        "},
 6408        indoc! {"
 6409            /*
 6410             *ˇ Lorem ipsum dolor sit amet,
 6411             * consectetur adipiscing elit.
 6412             */
 6413            /*
 6414             *ˇ Lorem ipsum dolor sit amet,
 6415             * consectetur adipiscing elit.
 6416             */
 6417            /*
 6418             *ˇ Lorem ipsum dolor sit amet,
 6419             * consectetur adipiscing elit.
 6420             */
 6421        "},
 6422        rust_lang.clone(),
 6423        &mut cx,
 6424    );
 6425
 6426    // multiline block comment with inline start/end delimiters
 6427    assert_rewrap(
 6428        indoc! {"
 6429            /*ˇ Lorem ipsum dolor sit amet,
 6430             * consectetur adipiscing elit. */
 6431        "},
 6432        indoc! {"
 6433            /*
 6434             *ˇ Lorem ipsum dolor sit amet,
 6435             * consectetur adipiscing elit.
 6436             */
 6437        "},
 6438        rust_lang.clone(),
 6439        &mut cx,
 6440    );
 6441
 6442    // block comment rewrap still respects paragraph bounds
 6443    assert_rewrap(
 6444        indoc! {"
 6445            /*
 6446             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6447             *
 6448             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6449             */
 6450        "},
 6451        indoc! {"
 6452            /*
 6453             *ˇ Lorem ipsum dolor sit amet,
 6454             * consectetur adipiscing elit.
 6455             *
 6456             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6457             */
 6458        "},
 6459        rust_lang.clone(),
 6460        &mut cx,
 6461    );
 6462
 6463    // documentation comments
 6464    assert_rewrap(
 6465        indoc! {"
 6466            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6467            /**
 6468             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6469             */
 6470        "},
 6471        indoc! {"
 6472            /**
 6473             *ˇ Lorem ipsum dolor sit amet,
 6474             * consectetur adipiscing elit.
 6475             */
 6476            /**
 6477             *ˇ Lorem ipsum dolor sit amet,
 6478             * consectetur adipiscing elit.
 6479             */
 6480        "},
 6481        rust_lang.clone(),
 6482        &mut cx,
 6483    );
 6484
 6485    // different, adjacent comments
 6486    assert_rewrap(
 6487        indoc! {"
 6488            /**
 6489             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6490             */
 6491            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6492            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6493        "},
 6494        indoc! {"
 6495            /**
 6496             *ˇ Lorem ipsum dolor sit amet,
 6497             * consectetur adipiscing elit.
 6498             */
 6499            /*
 6500             *ˇ Lorem ipsum dolor sit amet,
 6501             * consectetur adipiscing elit.
 6502             */
 6503            //ˇ Lorem ipsum dolor sit amet,
 6504            // consectetur adipiscing elit.
 6505        "},
 6506        rust_lang.clone(),
 6507        &mut cx,
 6508    );
 6509
 6510    // selection w/ single short block comment
 6511    assert_rewrap(
 6512        indoc! {"
 6513            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6514        "},
 6515        indoc! {"
 6516            «/*
 6517             * Lorem ipsum dolor sit amet,
 6518             * consectetur adipiscing elit.
 6519             */ˇ»
 6520        "},
 6521        rust_lang.clone(),
 6522        &mut cx,
 6523    );
 6524
 6525    // rewrapping a single comment w/ abutting comments
 6526    assert_rewrap(
 6527        indoc! {"
 6528            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6529            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6530        "},
 6531        indoc! {"
 6532            /*
 6533             * ˇLorem ipsum dolor sit amet,
 6534             * consectetur adipiscing elit.
 6535             */
 6536            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6537        "},
 6538        rust_lang.clone(),
 6539        &mut cx,
 6540    );
 6541
 6542    // selection w/ non-abutting short block comments
 6543    assert_rewrap(
 6544        indoc! {"
 6545            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6546
 6547            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6548        "},
 6549        indoc! {"
 6550            «/*
 6551             * Lorem ipsum dolor sit amet,
 6552             * consectetur adipiscing elit.
 6553             */
 6554
 6555            /*
 6556             * Lorem ipsum dolor sit amet,
 6557             * consectetur adipiscing elit.
 6558             */ˇ»
 6559        "},
 6560        rust_lang.clone(),
 6561        &mut cx,
 6562    );
 6563
 6564    // selection of multiline block comments
 6565    assert_rewrap(
 6566        indoc! {"
 6567            «/* Lorem ipsum dolor sit amet,
 6568             * consectetur adipiscing elit. */ˇ»
 6569        "},
 6570        indoc! {"
 6571            «/*
 6572             * Lorem ipsum dolor sit amet,
 6573             * consectetur adipiscing elit.
 6574             */ˇ»
 6575        "},
 6576        rust_lang.clone(),
 6577        &mut cx,
 6578    );
 6579
 6580    // partial selection of multiline block comments
 6581    assert_rewrap(
 6582        indoc! {"
 6583            «/* Lorem ipsum dolor sit amet,ˇ»
 6584             * consectetur adipiscing elit. */
 6585            /* Lorem ipsum dolor sit amet,
 6586             «* consectetur adipiscing elit. */ˇ»
 6587        "},
 6588        indoc! {"
 6589            «/*
 6590             * Lorem ipsum dolor sit amet,ˇ»
 6591             * consectetur adipiscing elit. */
 6592            /* Lorem ipsum dolor sit amet,
 6593             «* consectetur adipiscing elit.
 6594             */ˇ»
 6595        "},
 6596        rust_lang.clone(),
 6597        &mut cx,
 6598    );
 6599
 6600    // selection w/ abutting short block comments
 6601    // TODO: should not be combined; should rewrap as 2 comments
 6602    assert_rewrap(
 6603        indoc! {"
 6604            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6605            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6606        "},
 6607        // desired behavior:
 6608        // indoc! {"
 6609        //     «/*
 6610        //      * Lorem ipsum dolor sit amet,
 6611        //      * consectetur adipiscing elit.
 6612        //      */
 6613        //     /*
 6614        //      * Lorem ipsum dolor sit amet,
 6615        //      * consectetur adipiscing elit.
 6616        //      */ˇ»
 6617        // "},
 6618        // actual behaviour:
 6619        indoc! {"
 6620            «/*
 6621             * Lorem ipsum dolor sit amet,
 6622             * consectetur adipiscing elit. Lorem
 6623             * ipsum dolor sit amet, consectetur
 6624             * adipiscing elit.
 6625             */ˇ»
 6626        "},
 6627        rust_lang.clone(),
 6628        &mut cx,
 6629    );
 6630
 6631    // TODO: same as above, but with delimiters on separate line
 6632    // assert_rewrap(
 6633    //     indoc! {"
 6634    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6635    //          */
 6636    //         /*
 6637    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6638    //     "},
 6639    //     // desired:
 6640    //     // indoc! {"
 6641    //     //     «/*
 6642    //     //      * Lorem ipsum dolor sit amet,
 6643    //     //      * consectetur adipiscing elit.
 6644    //     //      */
 6645    //     //     /*
 6646    //     //      * Lorem ipsum dolor sit amet,
 6647    //     //      * consectetur adipiscing elit.
 6648    //     //      */ˇ»
 6649    //     // "},
 6650    //     // actual: (but with trailing w/s on the empty lines)
 6651    //     indoc! {"
 6652    //         «/*
 6653    //          * Lorem ipsum dolor sit amet,
 6654    //          * consectetur adipiscing elit.
 6655    //          *
 6656    //          */
 6657    //         /*
 6658    //          *
 6659    //          * Lorem ipsum dolor sit amet,
 6660    //          * consectetur adipiscing elit.
 6661    //          */ˇ»
 6662    //     "},
 6663    //     rust_lang.clone(),
 6664    //     &mut cx,
 6665    // );
 6666
 6667    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6668    assert_rewrap(
 6669        indoc! {"
 6670            /*
 6671             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6672             */
 6673            /*
 6674             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6675            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6676        "},
 6677        // desired:
 6678        // indoc! {"
 6679        //     /*
 6680        //      *ˇ Lorem ipsum dolor sit amet,
 6681        //      * consectetur adipiscing elit.
 6682        //      */
 6683        //     /*
 6684        //      *ˇ Lorem ipsum dolor sit amet,
 6685        //      * consectetur adipiscing elit.
 6686        //      */
 6687        //     /*
 6688        //      *ˇ Lorem ipsum dolor sit amet
 6689        //      */ /* consectetur adipiscing elit. */
 6690        // "},
 6691        // actual:
 6692        indoc! {"
 6693            /*
 6694             //ˇ Lorem ipsum dolor sit amet,
 6695             // consectetur adipiscing elit.
 6696             */
 6697            /*
 6698             * //ˇ Lorem ipsum dolor sit amet,
 6699             * consectetur adipiscing elit.
 6700             */
 6701            /*
 6702             *ˇ Lorem ipsum dolor sit amet */ /*
 6703             * consectetur adipiscing elit.
 6704             */
 6705        "},
 6706        rust_lang,
 6707        &mut cx,
 6708    );
 6709
 6710    #[track_caller]
 6711    fn assert_rewrap(
 6712        unwrapped_text: &str,
 6713        wrapped_text: &str,
 6714        language: Arc<Language>,
 6715        cx: &mut EditorTestContext,
 6716    ) {
 6717        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6718        cx.set_state(unwrapped_text);
 6719        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6720        cx.assert_editor_state(wrapped_text);
 6721    }
 6722}
 6723
 6724#[gpui::test]
 6725async fn test_hard_wrap(cx: &mut TestAppContext) {
 6726    init_test(cx, |_| {});
 6727    let mut cx = EditorTestContext::new(cx).await;
 6728
 6729    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6730    cx.update_editor(|editor, _, cx| {
 6731        editor.set_hard_wrap(Some(14), cx);
 6732    });
 6733
 6734    cx.set_state(indoc!(
 6735        "
 6736        one two three ˇ
 6737        "
 6738    ));
 6739    cx.simulate_input("four");
 6740    cx.run_until_parked();
 6741
 6742    cx.assert_editor_state(indoc!(
 6743        "
 6744        one two three
 6745        fourˇ
 6746        "
 6747    ));
 6748
 6749    cx.update_editor(|editor, window, cx| {
 6750        editor.newline(&Default::default(), window, cx);
 6751    });
 6752    cx.run_until_parked();
 6753    cx.assert_editor_state(indoc!(
 6754        "
 6755        one two three
 6756        four
 6757        ˇ
 6758        "
 6759    ));
 6760
 6761    cx.simulate_input("five");
 6762    cx.run_until_parked();
 6763    cx.assert_editor_state(indoc!(
 6764        "
 6765        one two three
 6766        four
 6767        fiveˇ
 6768        "
 6769    ));
 6770
 6771    cx.update_editor(|editor, window, cx| {
 6772        editor.newline(&Default::default(), window, cx);
 6773    });
 6774    cx.run_until_parked();
 6775    cx.simulate_input("# ");
 6776    cx.run_until_parked();
 6777    cx.assert_editor_state(indoc!(
 6778        "
 6779        one two three
 6780        four
 6781        five
 6782        # ˇ
 6783        "
 6784    ));
 6785
 6786    cx.update_editor(|editor, window, cx| {
 6787        editor.newline(&Default::default(), window, cx);
 6788    });
 6789    cx.run_until_parked();
 6790    cx.assert_editor_state(indoc!(
 6791        "
 6792        one two three
 6793        four
 6794        five
 6795        #\x20
 6796 6797        "
 6798    ));
 6799
 6800    cx.simulate_input(" 6");
 6801    cx.run_until_parked();
 6802    cx.assert_editor_state(indoc!(
 6803        "
 6804        one two three
 6805        four
 6806        five
 6807        #
 6808        # 6ˇ
 6809        "
 6810    ));
 6811}
 6812
 6813#[gpui::test]
 6814async fn test_cut_line_ends(cx: &mut TestAppContext) {
 6815    init_test(cx, |_| {});
 6816
 6817    let mut cx = EditorTestContext::new(cx).await;
 6818
 6819    cx.set_state(indoc! {"The quick brownˇ"});
 6820    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 6821    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 6822
 6823    cx.set_state(indoc! {"The emacs foxˇ"});
 6824    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 6825    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 6826
 6827    cx.set_state(indoc! {"
 6828        The quick« brownˇ»
 6829        fox jumps overˇ
 6830        the lazy dog"});
 6831    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6832    cx.assert_editor_state(indoc! {"
 6833        The quickˇ
 6834        ˇthe lazy dog"});
 6835
 6836    cx.set_state(indoc! {"
 6837        The quick« brownˇ»
 6838        fox jumps overˇ
 6839        the lazy dog"});
 6840    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 6841    cx.assert_editor_state(indoc! {"
 6842        The quickˇ
 6843        fox jumps overˇthe lazy dog"});
 6844
 6845    cx.set_state(indoc! {"
 6846        The quick« brownˇ»
 6847        fox jumps overˇ
 6848        the lazy dog"});
 6849    cx.update_editor(|e, window, cx| {
 6850        e.cut_to_end_of_line(
 6851            &CutToEndOfLine {
 6852                stop_at_newlines: true,
 6853            },
 6854            window,
 6855            cx,
 6856        )
 6857    });
 6858    cx.assert_editor_state(indoc! {"
 6859        The quickˇ
 6860        fox jumps overˇ
 6861        the lazy dog"});
 6862
 6863    cx.set_state(indoc! {"
 6864        The quick« brownˇ»
 6865        fox jumps overˇ
 6866        the lazy dog"});
 6867    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 6868    cx.assert_editor_state(indoc! {"
 6869        The quickˇ
 6870        fox jumps overˇthe lazy dog"});
 6871}
 6872
 6873#[gpui::test]
 6874async fn test_clipboard(cx: &mut TestAppContext) {
 6875    init_test(cx, |_| {});
 6876
 6877    let mut cx = EditorTestContext::new(cx).await;
 6878
 6879    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 6880    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6881    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 6882
 6883    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 6884    cx.set_state("two ˇfour ˇsix ˇ");
 6885    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6886    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 6887
 6888    // Paste again but with only two cursors. Since the number of cursors doesn't
 6889    // match the number of slices in the clipboard, the entire clipboard text
 6890    // is pasted at each cursor.
 6891    cx.set_state("ˇtwo one✅ four three six five ˇ");
 6892    cx.update_editor(|e, window, cx| {
 6893        e.handle_input("( ", window, cx);
 6894        e.paste(&Paste, window, cx);
 6895        e.handle_input(") ", window, cx);
 6896    });
 6897    cx.assert_editor_state(
 6898        &([
 6899            "( one✅ ",
 6900            "three ",
 6901            "five ) ˇtwo one✅ four three six five ( one✅ ",
 6902            "three ",
 6903            "five ) ˇ",
 6904        ]
 6905        .join("\n")),
 6906    );
 6907
 6908    // Cut with three selections, one of which is full-line.
 6909    cx.set_state(indoc! {"
 6910        1«2ˇ»3
 6911        4ˇ567
 6912        «8ˇ»9"});
 6913    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6914    cx.assert_editor_state(indoc! {"
 6915        1ˇ3
 6916        ˇ9"});
 6917
 6918    // Paste with three selections, noticing how the copied selection that was full-line
 6919    // gets inserted before the second cursor.
 6920    cx.set_state(indoc! {"
 6921        1ˇ3
 6922 6923        «oˇ»ne"});
 6924    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6925    cx.assert_editor_state(indoc! {"
 6926        12ˇ3
 6927        4567
 6928 6929        8ˇne"});
 6930
 6931    // Copy with a single cursor only, which writes the whole line into the clipboard.
 6932    cx.set_state(indoc! {"
 6933        The quick brown
 6934        fox juˇmps over
 6935        the lazy dog"});
 6936    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6937    assert_eq!(
 6938        cx.read_from_clipboard()
 6939            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6940        Some("fox jumps over\n".to_string())
 6941    );
 6942
 6943    // Paste with three selections, noticing how the copied full-line selection is inserted
 6944    // before the empty selections but replaces the selection that is non-empty.
 6945    cx.set_state(indoc! {"
 6946        Tˇhe quick brown
 6947        «foˇ»x jumps over
 6948        tˇhe lazy dog"});
 6949    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6950    cx.assert_editor_state(indoc! {"
 6951        fox jumps over
 6952        Tˇhe quick brown
 6953        fox jumps over
 6954        ˇx jumps over
 6955        fox jumps over
 6956        tˇhe lazy dog"});
 6957}
 6958
 6959#[gpui::test]
 6960async fn test_copy_trim(cx: &mut TestAppContext) {
 6961    init_test(cx, |_| {});
 6962
 6963    let mut cx = EditorTestContext::new(cx).await;
 6964    cx.set_state(
 6965        r#"            «for selection in selections.iter() {
 6966            let mut start = selection.start;
 6967            let mut end = selection.end;
 6968            let is_entire_line = selection.is_empty();
 6969            if is_entire_line {
 6970                start = Point::new(start.row, 0);ˇ»
 6971                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6972            }
 6973        "#,
 6974    );
 6975    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6976    assert_eq!(
 6977        cx.read_from_clipboard()
 6978            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6979        Some(
 6980            "for selection in selections.iter() {
 6981            let mut start = selection.start;
 6982            let mut end = selection.end;
 6983            let is_entire_line = selection.is_empty();
 6984            if is_entire_line {
 6985                start = Point::new(start.row, 0);"
 6986                .to_string()
 6987        ),
 6988        "Regular copying preserves all indentation selected",
 6989    );
 6990    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6991    assert_eq!(
 6992        cx.read_from_clipboard()
 6993            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6994        Some(
 6995            "for selection in selections.iter() {
 6996let mut start = selection.start;
 6997let mut end = selection.end;
 6998let is_entire_line = selection.is_empty();
 6999if is_entire_line {
 7000    start = Point::new(start.row, 0);"
 7001                .to_string()
 7002        ),
 7003        "Copying with stripping should strip all leading whitespaces"
 7004    );
 7005
 7006    cx.set_state(
 7007        r#"       «     for selection in selections.iter() {
 7008            let mut start = selection.start;
 7009            let mut end = selection.end;
 7010            let is_entire_line = selection.is_empty();
 7011            if is_entire_line {
 7012                start = Point::new(start.row, 0);ˇ»
 7013                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7014            }
 7015        "#,
 7016    );
 7017    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7018    assert_eq!(
 7019        cx.read_from_clipboard()
 7020            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7021        Some(
 7022            "     for selection in selections.iter() {
 7023            let mut start = selection.start;
 7024            let mut end = selection.end;
 7025            let is_entire_line = selection.is_empty();
 7026            if is_entire_line {
 7027                start = Point::new(start.row, 0);"
 7028                .to_string()
 7029        ),
 7030        "Regular copying preserves all indentation selected",
 7031    );
 7032    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7033    assert_eq!(
 7034        cx.read_from_clipboard()
 7035            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7036        Some(
 7037            "for selection in selections.iter() {
 7038let mut start = selection.start;
 7039let mut end = selection.end;
 7040let is_entire_line = selection.is_empty();
 7041if is_entire_line {
 7042    start = Point::new(start.row, 0);"
 7043                .to_string()
 7044        ),
 7045        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7046    );
 7047
 7048    cx.set_state(
 7049        r#"       «ˇ     for selection in selections.iter() {
 7050            let mut start = selection.start;
 7051            let mut end = selection.end;
 7052            let is_entire_line = selection.is_empty();
 7053            if is_entire_line {
 7054                start = Point::new(start.row, 0);»
 7055                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7056            }
 7057        "#,
 7058    );
 7059    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7060    assert_eq!(
 7061        cx.read_from_clipboard()
 7062            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7063        Some(
 7064            "     for selection in selections.iter() {
 7065            let mut start = selection.start;
 7066            let mut end = selection.end;
 7067            let is_entire_line = selection.is_empty();
 7068            if is_entire_line {
 7069                start = Point::new(start.row, 0);"
 7070                .to_string()
 7071        ),
 7072        "Regular copying for reverse selection works the same",
 7073    );
 7074    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7075    assert_eq!(
 7076        cx.read_from_clipboard()
 7077            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7078        Some(
 7079            "for selection in selections.iter() {
 7080let mut start = selection.start;
 7081let mut end = selection.end;
 7082let is_entire_line = selection.is_empty();
 7083if is_entire_line {
 7084    start = Point::new(start.row, 0);"
 7085                .to_string()
 7086        ),
 7087        "Copying with stripping for reverse selection works the same"
 7088    );
 7089
 7090    cx.set_state(
 7091        r#"            for selection «in selections.iter() {
 7092            let mut start = selection.start;
 7093            let mut end = selection.end;
 7094            let is_entire_line = selection.is_empty();
 7095            if is_entire_line {
 7096                start = Point::new(start.row, 0);ˇ»
 7097                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7098            }
 7099        "#,
 7100    );
 7101    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7102    assert_eq!(
 7103        cx.read_from_clipboard()
 7104            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7105        Some(
 7106            "in selections.iter() {
 7107            let mut start = selection.start;
 7108            let mut end = selection.end;
 7109            let is_entire_line = selection.is_empty();
 7110            if is_entire_line {
 7111                start = Point::new(start.row, 0);"
 7112                .to_string()
 7113        ),
 7114        "When selecting past the indent, the copying works as usual",
 7115    );
 7116    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7117    assert_eq!(
 7118        cx.read_from_clipboard()
 7119            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7120        Some(
 7121            "in selections.iter() {
 7122            let mut start = selection.start;
 7123            let mut end = selection.end;
 7124            let is_entire_line = selection.is_empty();
 7125            if is_entire_line {
 7126                start = Point::new(start.row, 0);"
 7127                .to_string()
 7128        ),
 7129        "When selecting past the indent, nothing is trimmed"
 7130    );
 7131
 7132    cx.set_state(
 7133        r#"            «for selection in selections.iter() {
 7134            let mut start = selection.start;
 7135
 7136            let mut end = selection.end;
 7137            let is_entire_line = selection.is_empty();
 7138            if is_entire_line {
 7139                start = Point::new(start.row, 0);
 7140ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7141            }
 7142        "#,
 7143    );
 7144    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7145    assert_eq!(
 7146        cx.read_from_clipboard()
 7147            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7148        Some(
 7149            "for selection in selections.iter() {
 7150let mut start = selection.start;
 7151
 7152let mut end = selection.end;
 7153let is_entire_line = selection.is_empty();
 7154if is_entire_line {
 7155    start = Point::new(start.row, 0);
 7156"
 7157            .to_string()
 7158        ),
 7159        "Copying with stripping should ignore empty lines"
 7160    );
 7161}
 7162
 7163#[gpui::test]
 7164async fn test_paste_multiline(cx: &mut TestAppContext) {
 7165    init_test(cx, |_| {});
 7166
 7167    let mut cx = EditorTestContext::new(cx).await;
 7168    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7169
 7170    // Cut an indented block, without the leading whitespace.
 7171    cx.set_state(indoc! {"
 7172        const a: B = (
 7173            c(),
 7174            «d(
 7175                e,
 7176                f
 7177            )ˇ»
 7178        );
 7179    "});
 7180    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7181    cx.assert_editor_state(indoc! {"
 7182        const a: B = (
 7183            c(),
 7184            ˇ
 7185        );
 7186    "});
 7187
 7188    // Paste it at the same position.
 7189    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7190    cx.assert_editor_state(indoc! {"
 7191        const a: B = (
 7192            c(),
 7193            d(
 7194                e,
 7195                f
 7196 7197        );
 7198    "});
 7199
 7200    // Paste it at a line with a lower indent level.
 7201    cx.set_state(indoc! {"
 7202        ˇ
 7203        const a: B = (
 7204            c(),
 7205        );
 7206    "});
 7207    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7208    cx.assert_editor_state(indoc! {"
 7209        d(
 7210            e,
 7211            f
 7212 7213        const a: B = (
 7214            c(),
 7215        );
 7216    "});
 7217
 7218    // Cut an indented block, with the leading whitespace.
 7219    cx.set_state(indoc! {"
 7220        const a: B = (
 7221            c(),
 7222        «    d(
 7223                e,
 7224                f
 7225            )
 7226        ˇ»);
 7227    "});
 7228    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7229    cx.assert_editor_state(indoc! {"
 7230        const a: B = (
 7231            c(),
 7232        ˇ);
 7233    "});
 7234
 7235    // Paste it at the same position.
 7236    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7237    cx.assert_editor_state(indoc! {"
 7238        const a: B = (
 7239            c(),
 7240            d(
 7241                e,
 7242                f
 7243            )
 7244        ˇ);
 7245    "});
 7246
 7247    // Paste it at a line with a higher indent level.
 7248    cx.set_state(indoc! {"
 7249        const a: B = (
 7250            c(),
 7251            d(
 7252                e,
 7253 7254            )
 7255        );
 7256    "});
 7257    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7258    cx.assert_editor_state(indoc! {"
 7259        const a: B = (
 7260            c(),
 7261            d(
 7262                e,
 7263                f    d(
 7264                    e,
 7265                    f
 7266                )
 7267        ˇ
 7268            )
 7269        );
 7270    "});
 7271
 7272    // Copy an indented block, starting mid-line
 7273    cx.set_state(indoc! {"
 7274        const a: B = (
 7275            c(),
 7276            somethin«g(
 7277                e,
 7278                f
 7279            )ˇ»
 7280        );
 7281    "});
 7282    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7283
 7284    // Paste it on a line with a lower indent level
 7285    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7286    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7287    cx.assert_editor_state(indoc! {"
 7288        const a: B = (
 7289            c(),
 7290            something(
 7291                e,
 7292                f
 7293            )
 7294        );
 7295        g(
 7296            e,
 7297            f
 7298"});
 7299}
 7300
 7301#[gpui::test]
 7302async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7303    init_test(cx, |_| {});
 7304
 7305    cx.write_to_clipboard(ClipboardItem::new_string(
 7306        "    d(\n        e\n    );\n".into(),
 7307    ));
 7308
 7309    let mut cx = EditorTestContext::new(cx).await;
 7310    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7311
 7312    cx.set_state(indoc! {"
 7313        fn a() {
 7314            b();
 7315            if c() {
 7316                ˇ
 7317            }
 7318        }
 7319    "});
 7320
 7321    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7322    cx.assert_editor_state(indoc! {"
 7323        fn a() {
 7324            b();
 7325            if c() {
 7326                d(
 7327                    e
 7328                );
 7329        ˇ
 7330            }
 7331        }
 7332    "});
 7333
 7334    cx.set_state(indoc! {"
 7335        fn a() {
 7336            b();
 7337            ˇ
 7338        }
 7339    "});
 7340
 7341    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7342    cx.assert_editor_state(indoc! {"
 7343        fn a() {
 7344            b();
 7345            d(
 7346                e
 7347            );
 7348        ˇ
 7349        }
 7350    "});
 7351}
 7352
 7353#[gpui::test]
 7354fn test_select_all(cx: &mut TestAppContext) {
 7355    init_test(cx, |_| {});
 7356
 7357    let editor = cx.add_window(|window, cx| {
 7358        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7359        build_editor(buffer, window, cx)
 7360    });
 7361    _ = editor.update(cx, |editor, window, cx| {
 7362        editor.select_all(&SelectAll, window, cx);
 7363        assert_eq!(
 7364            editor.selections.display_ranges(cx),
 7365            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7366        );
 7367    });
 7368}
 7369
 7370#[gpui::test]
 7371fn test_select_line(cx: &mut TestAppContext) {
 7372    init_test(cx, |_| {});
 7373
 7374    let editor = cx.add_window(|window, cx| {
 7375        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7376        build_editor(buffer, window, cx)
 7377    });
 7378    _ = editor.update(cx, |editor, window, cx| {
 7379        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7380            s.select_display_ranges([
 7381                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7382                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7383                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7384                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7385            ])
 7386        });
 7387        editor.select_line(&SelectLine, window, cx);
 7388        assert_eq!(
 7389            editor.selections.display_ranges(cx),
 7390            vec![
 7391                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7392                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7393            ]
 7394        );
 7395    });
 7396
 7397    _ = editor.update(cx, |editor, window, cx| {
 7398        editor.select_line(&SelectLine, window, cx);
 7399        assert_eq!(
 7400            editor.selections.display_ranges(cx),
 7401            vec![
 7402                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7403                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7404            ]
 7405        );
 7406    });
 7407
 7408    _ = editor.update(cx, |editor, window, cx| {
 7409        editor.select_line(&SelectLine, window, cx);
 7410        assert_eq!(
 7411            editor.selections.display_ranges(cx),
 7412            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7413        );
 7414    });
 7415}
 7416
 7417#[gpui::test]
 7418async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7419    init_test(cx, |_| {});
 7420    let mut cx = EditorTestContext::new(cx).await;
 7421
 7422    #[track_caller]
 7423    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7424        cx.set_state(initial_state);
 7425        cx.update_editor(|e, window, cx| {
 7426            e.split_selection_into_lines(&Default::default(), window, cx)
 7427        });
 7428        cx.assert_editor_state(expected_state);
 7429    }
 7430
 7431    // Selection starts and ends at the middle of lines, left-to-right
 7432    test(
 7433        &mut cx,
 7434        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7435        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7436    );
 7437    // Same thing, right-to-left
 7438    test(
 7439        &mut cx,
 7440        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7441        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7442    );
 7443
 7444    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7445    test(
 7446        &mut cx,
 7447        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7448        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7449    );
 7450    // Same thing, right-to-left
 7451    test(
 7452        &mut cx,
 7453        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7454        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7455    );
 7456
 7457    // Whole buffer, left-to-right, last line ends with newline
 7458    test(
 7459        &mut cx,
 7460        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7461        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7462    );
 7463    // Same thing, right-to-left
 7464    test(
 7465        &mut cx,
 7466        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7467        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7468    );
 7469
 7470    // Starts at the end of a line, ends at the start of another
 7471    test(
 7472        &mut cx,
 7473        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7474        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7475    );
 7476}
 7477
 7478#[gpui::test]
 7479async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7480    init_test(cx, |_| {});
 7481
 7482    let editor = cx.add_window(|window, cx| {
 7483        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7484        build_editor(buffer, window, cx)
 7485    });
 7486
 7487    // setup
 7488    _ = editor.update(cx, |editor, window, cx| {
 7489        editor.fold_creases(
 7490            vec![
 7491                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7492                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7493                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7494            ],
 7495            true,
 7496            window,
 7497            cx,
 7498        );
 7499        assert_eq!(
 7500            editor.display_text(cx),
 7501            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7502        );
 7503    });
 7504
 7505    _ = editor.update(cx, |editor, window, cx| {
 7506        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7507            s.select_display_ranges([
 7508                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7509                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7510                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7511                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7512            ])
 7513        });
 7514        editor.split_selection_into_lines(&Default::default(), window, cx);
 7515        assert_eq!(
 7516            editor.display_text(cx),
 7517            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7518        );
 7519    });
 7520    EditorTestContext::for_editor(editor, cx)
 7521        .await
 7522        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7523
 7524    _ = editor.update(cx, |editor, window, cx| {
 7525        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7526            s.select_display_ranges([
 7527                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7528            ])
 7529        });
 7530        editor.split_selection_into_lines(&Default::default(), window, cx);
 7531        assert_eq!(
 7532            editor.display_text(cx),
 7533            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7534        );
 7535        assert_eq!(
 7536            editor.selections.display_ranges(cx),
 7537            [
 7538                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7539                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7540                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7541                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7542                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7543                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7544                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7545            ]
 7546        );
 7547    });
 7548    EditorTestContext::for_editor(editor, cx)
 7549        .await
 7550        .assert_editor_state(
 7551            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7552        );
 7553}
 7554
 7555#[gpui::test]
 7556async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7557    init_test(cx, |_| {});
 7558
 7559    let mut cx = EditorTestContext::new(cx).await;
 7560
 7561    cx.set_state(indoc!(
 7562        r#"abc
 7563           defˇghi
 7564
 7565           jk
 7566           nlmo
 7567           "#
 7568    ));
 7569
 7570    cx.update_editor(|editor, window, cx| {
 7571        editor.add_selection_above(&Default::default(), window, cx);
 7572    });
 7573
 7574    cx.assert_editor_state(indoc!(
 7575        r#"abcˇ
 7576           defˇghi
 7577
 7578           jk
 7579           nlmo
 7580           "#
 7581    ));
 7582
 7583    cx.update_editor(|editor, window, cx| {
 7584        editor.add_selection_above(&Default::default(), window, cx);
 7585    });
 7586
 7587    cx.assert_editor_state(indoc!(
 7588        r#"abcˇ
 7589            defˇghi
 7590
 7591            jk
 7592            nlmo
 7593            "#
 7594    ));
 7595
 7596    cx.update_editor(|editor, window, cx| {
 7597        editor.add_selection_below(&Default::default(), window, cx);
 7598    });
 7599
 7600    cx.assert_editor_state(indoc!(
 7601        r#"abc
 7602           defˇghi
 7603
 7604           jk
 7605           nlmo
 7606           "#
 7607    ));
 7608
 7609    cx.update_editor(|editor, window, cx| {
 7610        editor.undo_selection(&Default::default(), window, cx);
 7611    });
 7612
 7613    cx.assert_editor_state(indoc!(
 7614        r#"abcˇ
 7615           defˇghi
 7616
 7617           jk
 7618           nlmo
 7619           "#
 7620    ));
 7621
 7622    cx.update_editor(|editor, window, cx| {
 7623        editor.redo_selection(&Default::default(), window, cx);
 7624    });
 7625
 7626    cx.assert_editor_state(indoc!(
 7627        r#"abc
 7628           defˇghi
 7629
 7630           jk
 7631           nlmo
 7632           "#
 7633    ));
 7634
 7635    cx.update_editor(|editor, window, cx| {
 7636        editor.add_selection_below(&Default::default(), window, cx);
 7637    });
 7638
 7639    cx.assert_editor_state(indoc!(
 7640        r#"abc
 7641           defˇghi
 7642           ˇ
 7643           jk
 7644           nlmo
 7645           "#
 7646    ));
 7647
 7648    cx.update_editor(|editor, window, cx| {
 7649        editor.add_selection_below(&Default::default(), window, cx);
 7650    });
 7651
 7652    cx.assert_editor_state(indoc!(
 7653        r#"abc
 7654           defˇghi
 7655           ˇ
 7656           jkˇ
 7657           nlmo
 7658           "#
 7659    ));
 7660
 7661    cx.update_editor(|editor, window, cx| {
 7662        editor.add_selection_below(&Default::default(), window, cx);
 7663    });
 7664
 7665    cx.assert_editor_state(indoc!(
 7666        r#"abc
 7667           defˇghi
 7668           ˇ
 7669           jkˇ
 7670           nlmˇo
 7671           "#
 7672    ));
 7673
 7674    cx.update_editor(|editor, window, cx| {
 7675        editor.add_selection_below(&Default::default(), window, cx);
 7676    });
 7677
 7678    cx.assert_editor_state(indoc!(
 7679        r#"abc
 7680           defˇghi
 7681           ˇ
 7682           jkˇ
 7683           nlmˇo
 7684           ˇ"#
 7685    ));
 7686
 7687    // change selections
 7688    cx.set_state(indoc!(
 7689        r#"abc
 7690           def«ˇg»hi
 7691
 7692           jk
 7693           nlmo
 7694           "#
 7695    ));
 7696
 7697    cx.update_editor(|editor, window, cx| {
 7698        editor.add_selection_below(&Default::default(), window, cx);
 7699    });
 7700
 7701    cx.assert_editor_state(indoc!(
 7702        r#"abc
 7703           def«ˇg»hi
 7704
 7705           jk
 7706           nlm«ˇo»
 7707           "#
 7708    ));
 7709
 7710    cx.update_editor(|editor, window, cx| {
 7711        editor.add_selection_below(&Default::default(), window, cx);
 7712    });
 7713
 7714    cx.assert_editor_state(indoc!(
 7715        r#"abc
 7716           def«ˇg»hi
 7717
 7718           jk
 7719           nlm«ˇo»
 7720           "#
 7721    ));
 7722
 7723    cx.update_editor(|editor, window, cx| {
 7724        editor.add_selection_above(&Default::default(), window, cx);
 7725    });
 7726
 7727    cx.assert_editor_state(indoc!(
 7728        r#"abc
 7729           def«ˇg»hi
 7730
 7731           jk
 7732           nlmo
 7733           "#
 7734    ));
 7735
 7736    cx.update_editor(|editor, window, cx| {
 7737        editor.add_selection_above(&Default::default(), window, cx);
 7738    });
 7739
 7740    cx.assert_editor_state(indoc!(
 7741        r#"abc
 7742           def«ˇg»hi
 7743
 7744           jk
 7745           nlmo
 7746           "#
 7747    ));
 7748
 7749    // Change selections again
 7750    cx.set_state(indoc!(
 7751        r#"a«bc
 7752           defgˇ»hi
 7753
 7754           jk
 7755           nlmo
 7756           "#
 7757    ));
 7758
 7759    cx.update_editor(|editor, window, cx| {
 7760        editor.add_selection_below(&Default::default(), window, cx);
 7761    });
 7762
 7763    cx.assert_editor_state(indoc!(
 7764        r#"a«bcˇ»
 7765           d«efgˇ»hi
 7766
 7767           j«kˇ»
 7768           nlmo
 7769           "#
 7770    ));
 7771
 7772    cx.update_editor(|editor, window, cx| {
 7773        editor.add_selection_below(&Default::default(), window, cx);
 7774    });
 7775    cx.assert_editor_state(indoc!(
 7776        r#"a«bcˇ»
 7777           d«efgˇ»hi
 7778
 7779           j«kˇ»
 7780           n«lmoˇ»
 7781           "#
 7782    ));
 7783    cx.update_editor(|editor, window, cx| {
 7784        editor.add_selection_above(&Default::default(), window, cx);
 7785    });
 7786
 7787    cx.assert_editor_state(indoc!(
 7788        r#"a«bcˇ»
 7789           d«efgˇ»hi
 7790
 7791           j«kˇ»
 7792           nlmo
 7793           "#
 7794    ));
 7795
 7796    // Change selections again
 7797    cx.set_state(indoc!(
 7798        r#"abc
 7799           d«ˇefghi
 7800
 7801           jk
 7802           nlm»o
 7803           "#
 7804    ));
 7805
 7806    cx.update_editor(|editor, window, cx| {
 7807        editor.add_selection_above(&Default::default(), window, cx);
 7808    });
 7809
 7810    cx.assert_editor_state(indoc!(
 7811        r#"a«ˇbc»
 7812           d«ˇef»ghi
 7813
 7814           j«ˇk»
 7815           n«ˇlm»o
 7816           "#
 7817    ));
 7818
 7819    cx.update_editor(|editor, window, cx| {
 7820        editor.add_selection_below(&Default::default(), window, cx);
 7821    });
 7822
 7823    cx.assert_editor_state(indoc!(
 7824        r#"abc
 7825           d«ˇef»ghi
 7826
 7827           j«ˇk»
 7828           n«ˇlm»o
 7829           "#
 7830    ));
 7831}
 7832
 7833#[gpui::test]
 7834async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 7835    init_test(cx, |_| {});
 7836    let mut cx = EditorTestContext::new(cx).await;
 7837
 7838    cx.set_state(indoc!(
 7839        r#"line onˇe
 7840           liˇne two
 7841           line three
 7842           line four"#
 7843    ));
 7844
 7845    cx.update_editor(|editor, window, cx| {
 7846        editor.add_selection_below(&Default::default(), window, cx);
 7847    });
 7848
 7849    // test multiple cursors expand in the same direction
 7850    cx.assert_editor_state(indoc!(
 7851        r#"line onˇe
 7852           liˇne twˇo
 7853           liˇne three
 7854           line four"#
 7855    ));
 7856
 7857    cx.update_editor(|editor, window, cx| {
 7858        editor.add_selection_below(&Default::default(), window, cx);
 7859    });
 7860
 7861    cx.update_editor(|editor, window, cx| {
 7862        editor.add_selection_below(&Default::default(), window, cx);
 7863    });
 7864
 7865    // test multiple cursors expand below overflow
 7866    cx.assert_editor_state(indoc!(
 7867        r#"line onˇe
 7868           liˇne twˇo
 7869           liˇne thˇree
 7870           liˇne foˇur"#
 7871    ));
 7872
 7873    cx.update_editor(|editor, window, cx| {
 7874        editor.add_selection_above(&Default::default(), window, cx);
 7875    });
 7876
 7877    // test multiple cursors retrieves back correctly
 7878    cx.assert_editor_state(indoc!(
 7879        r#"line onˇe
 7880           liˇne twˇo
 7881           liˇne thˇree
 7882           line four"#
 7883    ));
 7884
 7885    cx.update_editor(|editor, window, cx| {
 7886        editor.add_selection_above(&Default::default(), window, cx);
 7887    });
 7888
 7889    cx.update_editor(|editor, window, cx| {
 7890        editor.add_selection_above(&Default::default(), window, cx);
 7891    });
 7892
 7893    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 7894    cx.assert_editor_state(indoc!(
 7895        r#"liˇne onˇe
 7896           liˇne two
 7897           line three
 7898           line four"#
 7899    ));
 7900
 7901    cx.update_editor(|editor, window, cx| {
 7902        editor.undo_selection(&Default::default(), window, cx);
 7903    });
 7904
 7905    // test undo
 7906    cx.assert_editor_state(indoc!(
 7907        r#"line onˇe
 7908           liˇne twˇo
 7909           line three
 7910           line four"#
 7911    ));
 7912
 7913    cx.update_editor(|editor, window, cx| {
 7914        editor.redo_selection(&Default::default(), window, cx);
 7915    });
 7916
 7917    // test redo
 7918    cx.assert_editor_state(indoc!(
 7919        r#"liˇne onˇe
 7920           liˇne two
 7921           line three
 7922           line four"#
 7923    ));
 7924
 7925    cx.set_state(indoc!(
 7926        r#"abcd
 7927           ef«ghˇ»
 7928           ijkl
 7929           «mˇ»nop"#
 7930    ));
 7931
 7932    cx.update_editor(|editor, window, cx| {
 7933        editor.add_selection_above(&Default::default(), window, cx);
 7934    });
 7935
 7936    // test multiple selections expand in the same direction
 7937    cx.assert_editor_state(indoc!(
 7938        r#"ab«cdˇ»
 7939           ef«ghˇ»
 7940           «iˇ»jkl
 7941           «mˇ»nop"#
 7942    ));
 7943
 7944    cx.update_editor(|editor, window, cx| {
 7945        editor.add_selection_above(&Default::default(), window, cx);
 7946    });
 7947
 7948    // test multiple selection upward overflow
 7949    cx.assert_editor_state(indoc!(
 7950        r#"ab«cdˇ»
 7951           «eˇ»f«ghˇ»
 7952           «iˇ»jkl
 7953           «mˇ»nop"#
 7954    ));
 7955
 7956    cx.update_editor(|editor, window, cx| {
 7957        editor.add_selection_below(&Default::default(), window, cx);
 7958    });
 7959
 7960    // test multiple selection retrieves back correctly
 7961    cx.assert_editor_state(indoc!(
 7962        r#"abcd
 7963           ef«ghˇ»
 7964           «iˇ»jkl
 7965           «mˇ»nop"#
 7966    ));
 7967
 7968    cx.update_editor(|editor, window, cx| {
 7969        editor.add_selection_below(&Default::default(), window, cx);
 7970    });
 7971
 7972    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 7973    cx.assert_editor_state(indoc!(
 7974        r#"abcd
 7975           ef«ghˇ»
 7976           ij«klˇ»
 7977           «mˇ»nop"#
 7978    ));
 7979
 7980    cx.update_editor(|editor, window, cx| {
 7981        editor.undo_selection(&Default::default(), window, cx);
 7982    });
 7983
 7984    // test undo
 7985    cx.assert_editor_state(indoc!(
 7986        r#"abcd
 7987           ef«ghˇ»
 7988           «iˇ»jkl
 7989           «mˇ»nop"#
 7990    ));
 7991
 7992    cx.update_editor(|editor, window, cx| {
 7993        editor.redo_selection(&Default::default(), window, cx);
 7994    });
 7995
 7996    // test redo
 7997    cx.assert_editor_state(indoc!(
 7998        r#"abcd
 7999           ef«ghˇ»
 8000           ij«klˇ»
 8001           «mˇ»nop"#
 8002    ));
 8003}
 8004
 8005#[gpui::test]
 8006async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8007    init_test(cx, |_| {});
 8008    let mut cx = EditorTestContext::new(cx).await;
 8009
 8010    cx.set_state(indoc!(
 8011        r#"line onˇe
 8012           liˇne two
 8013           line three
 8014           line four"#
 8015    ));
 8016
 8017    cx.update_editor(|editor, window, cx| {
 8018        editor.add_selection_below(&Default::default(), window, cx);
 8019        editor.add_selection_below(&Default::default(), window, cx);
 8020        editor.add_selection_below(&Default::default(), window, cx);
 8021    });
 8022
 8023    // initial state with two multi cursor groups
 8024    cx.assert_editor_state(indoc!(
 8025        r#"line onˇe
 8026           liˇne twˇo
 8027           liˇne thˇree
 8028           liˇne foˇur"#
 8029    ));
 8030
 8031    // add single cursor in middle - simulate opt click
 8032    cx.update_editor(|editor, window, cx| {
 8033        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8034        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8035        editor.end_selection(window, cx);
 8036    });
 8037
 8038    cx.assert_editor_state(indoc!(
 8039        r#"line onˇe
 8040           liˇne twˇo
 8041           liˇneˇ thˇree
 8042           liˇne foˇur"#
 8043    ));
 8044
 8045    cx.update_editor(|editor, window, cx| {
 8046        editor.add_selection_above(&Default::default(), window, cx);
 8047    });
 8048
 8049    // test new added selection expands above and existing selection shrinks
 8050    cx.assert_editor_state(indoc!(
 8051        r#"line onˇe
 8052           liˇneˇ twˇo
 8053           liˇneˇ thˇree
 8054           line four"#
 8055    ));
 8056
 8057    cx.update_editor(|editor, window, cx| {
 8058        editor.add_selection_above(&Default::default(), window, cx);
 8059    });
 8060
 8061    // test new added selection expands above and existing selection shrinks
 8062    cx.assert_editor_state(indoc!(
 8063        r#"lineˇ onˇe
 8064           liˇneˇ twˇo
 8065           lineˇ three
 8066           line four"#
 8067    ));
 8068
 8069    // intial state with two selection groups
 8070    cx.set_state(indoc!(
 8071        r#"abcd
 8072           ef«ghˇ»
 8073           ijkl
 8074           «mˇ»nop"#
 8075    ));
 8076
 8077    cx.update_editor(|editor, window, cx| {
 8078        editor.add_selection_above(&Default::default(), window, cx);
 8079        editor.add_selection_above(&Default::default(), window, cx);
 8080    });
 8081
 8082    cx.assert_editor_state(indoc!(
 8083        r#"ab«cdˇ»
 8084           «eˇ»f«ghˇ»
 8085           «iˇ»jkl
 8086           «mˇ»nop"#
 8087    ));
 8088
 8089    // add single selection in middle - simulate opt drag
 8090    cx.update_editor(|editor, window, cx| {
 8091        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8092        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8093        editor.update_selection(
 8094            DisplayPoint::new(DisplayRow(2), 4),
 8095            0,
 8096            gpui::Point::<f32>::default(),
 8097            window,
 8098            cx,
 8099        );
 8100        editor.end_selection(window, cx);
 8101    });
 8102
 8103    cx.assert_editor_state(indoc!(
 8104        r#"ab«cdˇ»
 8105           «eˇ»f«ghˇ»
 8106           «iˇ»jk«lˇ»
 8107           «mˇ»nop"#
 8108    ));
 8109
 8110    cx.update_editor(|editor, window, cx| {
 8111        editor.add_selection_below(&Default::default(), window, cx);
 8112    });
 8113
 8114    // test new added selection expands below, others shrinks from above
 8115    cx.assert_editor_state(indoc!(
 8116        r#"abcd
 8117           ef«ghˇ»
 8118           «iˇ»jk«lˇ»
 8119           «mˇ»no«pˇ»"#
 8120    ));
 8121}
 8122
 8123#[gpui::test]
 8124async fn test_select_next(cx: &mut TestAppContext) {
 8125    init_test(cx, |_| {});
 8126
 8127    let mut cx = EditorTestContext::new(cx).await;
 8128    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8129
 8130    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8131        .unwrap();
 8132    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8133
 8134    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8135        .unwrap();
 8136    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8137
 8138    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8139    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8140
 8141    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8142    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8143
 8144    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8145        .unwrap();
 8146    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8147
 8148    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8149        .unwrap();
 8150    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8151
 8152    // Test selection direction should be preserved
 8153    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8154
 8155    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8156        .unwrap();
 8157    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8158}
 8159
 8160#[gpui::test]
 8161async fn test_select_all_matches(cx: &mut TestAppContext) {
 8162    init_test(cx, |_| {});
 8163
 8164    let mut cx = EditorTestContext::new(cx).await;
 8165
 8166    // Test caret-only selections
 8167    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8168    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8169        .unwrap();
 8170    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8171
 8172    // Test left-to-right selections
 8173    cx.set_state("abc\n«abcˇ»\nabc");
 8174    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8175        .unwrap();
 8176    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8177
 8178    // Test right-to-left selections
 8179    cx.set_state("abc\n«ˇabc»\nabc");
 8180    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8181        .unwrap();
 8182    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8183
 8184    // Test selecting whitespace with caret selection
 8185    cx.set_state("abc\nˇ   abc\nabc");
 8186    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8187        .unwrap();
 8188    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8189
 8190    // Test selecting whitespace with left-to-right selection
 8191    cx.set_state("abc\n«ˇ  »abc\nabc");
 8192    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8193        .unwrap();
 8194    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8195
 8196    // Test no matches with right-to-left selection
 8197    cx.set_state("abc\n«  ˇ»abc\nabc");
 8198    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8199        .unwrap();
 8200    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8201
 8202    // Test with a single word and clip_at_line_ends=true (#29823)
 8203    cx.set_state("aˇbc");
 8204    cx.update_editor(|e, window, cx| {
 8205        e.set_clip_at_line_ends(true, cx);
 8206        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8207        e.set_clip_at_line_ends(false, cx);
 8208    });
 8209    cx.assert_editor_state("«abcˇ»");
 8210}
 8211
 8212#[gpui::test]
 8213async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8214    init_test(cx, |_| {});
 8215
 8216    let mut cx = EditorTestContext::new(cx).await;
 8217
 8218    let large_body_1 = "\nd".repeat(200);
 8219    let large_body_2 = "\ne".repeat(200);
 8220
 8221    cx.set_state(&format!(
 8222        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8223    ));
 8224    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8225        let scroll_position = editor.scroll_position(cx);
 8226        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8227        scroll_position
 8228    });
 8229
 8230    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8231        .unwrap();
 8232    cx.assert_editor_state(&format!(
 8233        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8234    ));
 8235    let scroll_position_after_selection =
 8236        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8237    assert_eq!(
 8238        initial_scroll_position, scroll_position_after_selection,
 8239        "Scroll position should not change after selecting all matches"
 8240    );
 8241}
 8242
 8243#[gpui::test]
 8244async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8245    init_test(cx, |_| {});
 8246
 8247    let mut cx = EditorLspTestContext::new_rust(
 8248        lsp::ServerCapabilities {
 8249            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8250            ..Default::default()
 8251        },
 8252        cx,
 8253    )
 8254    .await;
 8255
 8256    cx.set_state(indoc! {"
 8257        line 1
 8258        line 2
 8259        linˇe 3
 8260        line 4
 8261        line 5
 8262    "});
 8263
 8264    // Make an edit
 8265    cx.update_editor(|editor, window, cx| {
 8266        editor.handle_input("X", window, cx);
 8267    });
 8268
 8269    // Move cursor to a different position
 8270    cx.update_editor(|editor, window, cx| {
 8271        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8272            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8273        });
 8274    });
 8275
 8276    cx.assert_editor_state(indoc! {"
 8277        line 1
 8278        line 2
 8279        linXe 3
 8280        line 4
 8281        liˇne 5
 8282    "});
 8283
 8284    cx.lsp
 8285        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8286            Ok(Some(vec![lsp::TextEdit::new(
 8287                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8288                "PREFIX ".to_string(),
 8289            )]))
 8290        });
 8291
 8292    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8293        .unwrap()
 8294        .await
 8295        .unwrap();
 8296
 8297    cx.assert_editor_state(indoc! {"
 8298        PREFIX line 1
 8299        line 2
 8300        linXe 3
 8301        line 4
 8302        liˇne 5
 8303    "});
 8304
 8305    // Undo formatting
 8306    cx.update_editor(|editor, window, cx| {
 8307        editor.undo(&Default::default(), window, cx);
 8308    });
 8309
 8310    // Verify cursor moved back to position after edit
 8311    cx.assert_editor_state(indoc! {"
 8312        line 1
 8313        line 2
 8314        linXˇe 3
 8315        line 4
 8316        line 5
 8317    "});
 8318}
 8319
 8320#[gpui::test]
 8321async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8322    init_test(cx, |_| {});
 8323
 8324    let mut cx = EditorTestContext::new(cx).await;
 8325
 8326    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8327    cx.update_editor(|editor, window, cx| {
 8328        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8329    });
 8330
 8331    cx.set_state(indoc! {"
 8332        line 1
 8333        line 2
 8334        linˇe 3
 8335        line 4
 8336        line 5
 8337        line 6
 8338        line 7
 8339        line 8
 8340        line 9
 8341        line 10
 8342    "});
 8343
 8344    let snapshot = cx.buffer_snapshot();
 8345    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8346
 8347    cx.update(|_, cx| {
 8348        provider.update(cx, |provider, _| {
 8349            provider.set_edit_prediction(Some(edit_prediction::EditPrediction::Local {
 8350                id: None,
 8351                edits: vec![(edit_position..edit_position, "X".into())],
 8352                edit_preview: None,
 8353            }))
 8354        })
 8355    });
 8356
 8357    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8358    cx.update_editor(|editor, window, cx| {
 8359        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8360    });
 8361
 8362    cx.assert_editor_state(indoc! {"
 8363        line 1
 8364        line 2
 8365        lineXˇ 3
 8366        line 4
 8367        line 5
 8368        line 6
 8369        line 7
 8370        line 8
 8371        line 9
 8372        line 10
 8373    "});
 8374
 8375    cx.update_editor(|editor, window, cx| {
 8376        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8377            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8378        });
 8379    });
 8380
 8381    cx.assert_editor_state(indoc! {"
 8382        line 1
 8383        line 2
 8384        lineX 3
 8385        line 4
 8386        line 5
 8387        line 6
 8388        line 7
 8389        line 8
 8390        line 9
 8391        liˇne 10
 8392    "});
 8393
 8394    cx.update_editor(|editor, window, cx| {
 8395        editor.undo(&Default::default(), window, cx);
 8396    });
 8397
 8398    cx.assert_editor_state(indoc! {"
 8399        line 1
 8400        line 2
 8401        lineˇ 3
 8402        line 4
 8403        line 5
 8404        line 6
 8405        line 7
 8406        line 8
 8407        line 9
 8408        line 10
 8409    "});
 8410}
 8411
 8412#[gpui::test]
 8413async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8414    init_test(cx, |_| {});
 8415
 8416    let mut cx = EditorTestContext::new(cx).await;
 8417    cx.set_state(
 8418        r#"let foo = 2;
 8419lˇet foo = 2;
 8420let fooˇ = 2;
 8421let foo = 2;
 8422let foo = ˇ2;"#,
 8423    );
 8424
 8425    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8426        .unwrap();
 8427    cx.assert_editor_state(
 8428        r#"let foo = 2;
 8429«letˇ» foo = 2;
 8430let «fooˇ» = 2;
 8431let foo = 2;
 8432let foo = «2ˇ»;"#,
 8433    );
 8434
 8435    // noop for multiple selections with different contents
 8436    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8437        .unwrap();
 8438    cx.assert_editor_state(
 8439        r#"let foo = 2;
 8440«letˇ» foo = 2;
 8441let «fooˇ» = 2;
 8442let foo = 2;
 8443let foo = «2ˇ»;"#,
 8444    );
 8445
 8446    // Test last selection direction should be preserved
 8447    cx.set_state(
 8448        r#"let foo = 2;
 8449let foo = 2;
 8450let «fooˇ» = 2;
 8451let «ˇfoo» = 2;
 8452let foo = 2;"#,
 8453    );
 8454
 8455    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8456        .unwrap();
 8457    cx.assert_editor_state(
 8458        r#"let foo = 2;
 8459let foo = 2;
 8460let «fooˇ» = 2;
 8461let «ˇfoo» = 2;
 8462let «ˇfoo» = 2;"#,
 8463    );
 8464}
 8465
 8466#[gpui::test]
 8467async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8468    init_test(cx, |_| {});
 8469
 8470    let mut cx =
 8471        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8472
 8473    cx.assert_editor_state(indoc! {"
 8474        ˇbbb
 8475        ccc
 8476
 8477        bbb
 8478        ccc
 8479        "});
 8480    cx.dispatch_action(SelectPrevious::default());
 8481    cx.assert_editor_state(indoc! {"
 8482                «bbbˇ»
 8483                ccc
 8484
 8485                bbb
 8486                ccc
 8487                "});
 8488    cx.dispatch_action(SelectPrevious::default());
 8489    cx.assert_editor_state(indoc! {"
 8490                «bbbˇ»
 8491                ccc
 8492
 8493                «bbbˇ»
 8494                ccc
 8495                "});
 8496}
 8497
 8498#[gpui::test]
 8499async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8500    init_test(cx, |_| {});
 8501
 8502    let mut cx = EditorTestContext::new(cx).await;
 8503    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8504
 8505    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8506        .unwrap();
 8507    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8508
 8509    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8510        .unwrap();
 8511    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8512
 8513    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8514    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8515
 8516    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8517    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8518
 8519    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8520        .unwrap();
 8521    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8522
 8523    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8524        .unwrap();
 8525    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8526}
 8527
 8528#[gpui::test]
 8529async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8530    init_test(cx, |_| {});
 8531
 8532    let mut cx = EditorTestContext::new(cx).await;
 8533    cx.set_state("");
 8534
 8535    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8536        .unwrap();
 8537    cx.assert_editor_state("«aˇ»");
 8538    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8539        .unwrap();
 8540    cx.assert_editor_state("«aˇ»");
 8541}
 8542
 8543#[gpui::test]
 8544async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8545    init_test(cx, |_| {});
 8546
 8547    let mut cx = EditorTestContext::new(cx).await;
 8548    cx.set_state(
 8549        r#"let foo = 2;
 8550lˇet foo = 2;
 8551let fooˇ = 2;
 8552let foo = 2;
 8553let foo = ˇ2;"#,
 8554    );
 8555
 8556    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8557        .unwrap();
 8558    cx.assert_editor_state(
 8559        r#"let foo = 2;
 8560«letˇ» foo = 2;
 8561let «fooˇ» = 2;
 8562let foo = 2;
 8563let foo = «2ˇ»;"#,
 8564    );
 8565
 8566    // noop for multiple selections with different contents
 8567    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8568        .unwrap();
 8569    cx.assert_editor_state(
 8570        r#"let foo = 2;
 8571«letˇ» foo = 2;
 8572let «fooˇ» = 2;
 8573let foo = 2;
 8574let foo = «2ˇ»;"#,
 8575    );
 8576}
 8577
 8578#[gpui::test]
 8579async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8580    init_test(cx, |_| {});
 8581
 8582    let mut cx = EditorTestContext::new(cx).await;
 8583    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8584
 8585    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8586        .unwrap();
 8587    // selection direction is preserved
 8588    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8589
 8590    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8591        .unwrap();
 8592    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8593
 8594    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8595    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8596
 8597    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8598    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8599
 8600    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8601        .unwrap();
 8602    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8603
 8604    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8605        .unwrap();
 8606    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8607}
 8608
 8609#[gpui::test]
 8610async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8611    init_test(cx, |_| {});
 8612
 8613    let language = Arc::new(Language::new(
 8614        LanguageConfig::default(),
 8615        Some(tree_sitter_rust::LANGUAGE.into()),
 8616    ));
 8617
 8618    let text = r#"
 8619        use mod1::mod2::{mod3, mod4};
 8620
 8621        fn fn_1(param1: bool, param2: &str) {
 8622            let var1 = "text";
 8623        }
 8624    "#
 8625    .unindent();
 8626
 8627    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8628    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8629    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8630
 8631    editor
 8632        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8633        .await;
 8634
 8635    editor.update_in(cx, |editor, window, cx| {
 8636        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8637            s.select_display_ranges([
 8638                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8639                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8640                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8641            ]);
 8642        });
 8643        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8644    });
 8645    editor.update(cx, |editor, cx| {
 8646        assert_text_with_selections(
 8647            editor,
 8648            indoc! {r#"
 8649                use mod1::mod2::{mod3, «mod4ˇ»};
 8650
 8651                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8652                    let var1 = "«ˇtext»";
 8653                }
 8654            "#},
 8655            cx,
 8656        );
 8657    });
 8658
 8659    editor.update_in(cx, |editor, window, cx| {
 8660        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8661    });
 8662    editor.update(cx, |editor, cx| {
 8663        assert_text_with_selections(
 8664            editor,
 8665            indoc! {r#"
 8666                use mod1::mod2::«{mod3, mod4}ˇ»;
 8667
 8668                «ˇfn fn_1(param1: bool, param2: &str) {
 8669                    let var1 = "text";
 8670 8671            "#},
 8672            cx,
 8673        );
 8674    });
 8675
 8676    editor.update_in(cx, |editor, window, cx| {
 8677        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8678    });
 8679    assert_eq!(
 8680        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8681        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8682    );
 8683
 8684    // Trying to expand the selected syntax node one more time has no effect.
 8685    editor.update_in(cx, |editor, window, cx| {
 8686        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8687    });
 8688    assert_eq!(
 8689        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8690        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8691    );
 8692
 8693    editor.update_in(cx, |editor, window, cx| {
 8694        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8695    });
 8696    editor.update(cx, |editor, cx| {
 8697        assert_text_with_selections(
 8698            editor,
 8699            indoc! {r#"
 8700                use mod1::mod2::«{mod3, mod4}ˇ»;
 8701
 8702                «ˇfn fn_1(param1: bool, param2: &str) {
 8703                    let var1 = "text";
 8704 8705            "#},
 8706            cx,
 8707        );
 8708    });
 8709
 8710    editor.update_in(cx, |editor, window, cx| {
 8711        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8712    });
 8713    editor.update(cx, |editor, cx| {
 8714        assert_text_with_selections(
 8715            editor,
 8716            indoc! {r#"
 8717                use mod1::mod2::{mod3, «mod4ˇ»};
 8718
 8719                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8720                    let var1 = "«ˇtext»";
 8721                }
 8722            "#},
 8723            cx,
 8724        );
 8725    });
 8726
 8727    editor.update_in(cx, |editor, window, cx| {
 8728        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8729    });
 8730    editor.update(cx, |editor, cx| {
 8731        assert_text_with_selections(
 8732            editor,
 8733            indoc! {r#"
 8734                use mod1::mod2::{mod3, moˇd4};
 8735
 8736                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8737                    let var1 = "teˇxt";
 8738                }
 8739            "#},
 8740            cx,
 8741        );
 8742    });
 8743
 8744    // Trying to shrink the selected syntax node one more time has no effect.
 8745    editor.update_in(cx, |editor, window, cx| {
 8746        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8747    });
 8748    editor.update_in(cx, |editor, _, cx| {
 8749        assert_text_with_selections(
 8750            editor,
 8751            indoc! {r#"
 8752                use mod1::mod2::{mod3, moˇd4};
 8753
 8754                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8755                    let var1 = "teˇxt";
 8756                }
 8757            "#},
 8758            cx,
 8759        );
 8760    });
 8761
 8762    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8763    // a fold.
 8764    editor.update_in(cx, |editor, window, cx| {
 8765        editor.fold_creases(
 8766            vec![
 8767                Crease::simple(
 8768                    Point::new(0, 21)..Point::new(0, 24),
 8769                    FoldPlaceholder::test(),
 8770                ),
 8771                Crease::simple(
 8772                    Point::new(3, 20)..Point::new(3, 22),
 8773                    FoldPlaceholder::test(),
 8774                ),
 8775            ],
 8776            true,
 8777            window,
 8778            cx,
 8779        );
 8780        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8781    });
 8782    editor.update(cx, |editor, cx| {
 8783        assert_text_with_selections(
 8784            editor,
 8785            indoc! {r#"
 8786                use mod1::mod2::«{mod3, mod4}ˇ»;
 8787
 8788                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8789                    let var1 = "«ˇtext»";
 8790                }
 8791            "#},
 8792            cx,
 8793        );
 8794    });
 8795}
 8796
 8797#[gpui::test]
 8798async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8799    init_test(cx, |_| {});
 8800
 8801    let language = Arc::new(Language::new(
 8802        LanguageConfig::default(),
 8803        Some(tree_sitter_rust::LANGUAGE.into()),
 8804    ));
 8805
 8806    let text = "let a = 2;";
 8807
 8808    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8809    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8810    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8811
 8812    editor
 8813        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8814        .await;
 8815
 8816    // Test case 1: Cursor at end of word
 8817    editor.update_in(cx, |editor, window, cx| {
 8818        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8819            s.select_display_ranges([
 8820                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 8821            ]);
 8822        });
 8823    });
 8824    editor.update(cx, |editor, cx| {
 8825        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 8826    });
 8827    editor.update_in(cx, |editor, window, cx| {
 8828        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8829    });
 8830    editor.update(cx, |editor, cx| {
 8831        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 8832    });
 8833    editor.update_in(cx, |editor, window, cx| {
 8834        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8835    });
 8836    editor.update(cx, |editor, cx| {
 8837        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8838    });
 8839
 8840    // Test case 2: Cursor at end of statement
 8841    editor.update_in(cx, |editor, window, cx| {
 8842        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8843            s.select_display_ranges([
 8844                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 8845            ]);
 8846        });
 8847    });
 8848    editor.update(cx, |editor, cx| {
 8849        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 8850    });
 8851    editor.update_in(cx, |editor, window, cx| {
 8852        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8853    });
 8854    editor.update(cx, |editor, cx| {
 8855        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8856    });
 8857}
 8858
 8859#[gpui::test]
 8860async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 8861    init_test(cx, |_| {});
 8862
 8863    let language = Arc::new(Language::new(
 8864        LanguageConfig {
 8865            name: "JavaScript".into(),
 8866            ..Default::default()
 8867        },
 8868        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8869    ));
 8870
 8871    let text = r#"
 8872        let a = {
 8873            key: "value",
 8874        };
 8875    "#
 8876    .unindent();
 8877
 8878    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8879    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8880    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8881
 8882    editor
 8883        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8884        .await;
 8885
 8886    // Test case 1: Cursor after '{'
 8887    editor.update_in(cx, |editor, window, cx| {
 8888        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8889            s.select_display_ranges([
 8890                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 8891            ]);
 8892        });
 8893    });
 8894    editor.update(cx, |editor, cx| {
 8895        assert_text_with_selections(
 8896            editor,
 8897            indoc! {r#"
 8898                let a = {ˇ
 8899                    key: "value",
 8900                };
 8901            "#},
 8902            cx,
 8903        );
 8904    });
 8905    editor.update_in(cx, |editor, window, cx| {
 8906        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8907    });
 8908    editor.update(cx, |editor, cx| {
 8909        assert_text_with_selections(
 8910            editor,
 8911            indoc! {r#"
 8912                let a = «ˇ{
 8913                    key: "value",
 8914                }»;
 8915            "#},
 8916            cx,
 8917        );
 8918    });
 8919
 8920    // Test case 2: Cursor after ':'
 8921    editor.update_in(cx, |editor, window, cx| {
 8922        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8923            s.select_display_ranges([
 8924                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 8925            ]);
 8926        });
 8927    });
 8928    editor.update(cx, |editor, cx| {
 8929        assert_text_with_selections(
 8930            editor,
 8931            indoc! {r#"
 8932                let a = {
 8933                    key:ˇ "value",
 8934                };
 8935            "#},
 8936            cx,
 8937        );
 8938    });
 8939    editor.update_in(cx, |editor, window, cx| {
 8940        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8941    });
 8942    editor.update(cx, |editor, cx| {
 8943        assert_text_with_selections(
 8944            editor,
 8945            indoc! {r#"
 8946                let a = {
 8947                    «ˇkey: "value"»,
 8948                };
 8949            "#},
 8950            cx,
 8951        );
 8952    });
 8953    editor.update_in(cx, |editor, window, cx| {
 8954        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8955    });
 8956    editor.update(cx, |editor, cx| {
 8957        assert_text_with_selections(
 8958            editor,
 8959            indoc! {r#"
 8960                let a = «ˇ{
 8961                    key: "value",
 8962                }»;
 8963            "#},
 8964            cx,
 8965        );
 8966    });
 8967
 8968    // Test case 3: Cursor after ','
 8969    editor.update_in(cx, |editor, window, cx| {
 8970        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8971            s.select_display_ranges([
 8972                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 8973            ]);
 8974        });
 8975    });
 8976    editor.update(cx, |editor, cx| {
 8977        assert_text_with_selections(
 8978            editor,
 8979            indoc! {r#"
 8980                let a = {
 8981                    key: "value",ˇ
 8982                };
 8983            "#},
 8984            cx,
 8985        );
 8986    });
 8987    editor.update_in(cx, |editor, window, cx| {
 8988        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8989    });
 8990    editor.update(cx, |editor, cx| {
 8991        assert_text_with_selections(
 8992            editor,
 8993            indoc! {r#"
 8994                let a = «ˇ{
 8995                    key: "value",
 8996                }»;
 8997            "#},
 8998            cx,
 8999        );
 9000    });
 9001
 9002    // Test case 4: Cursor after ';'
 9003    editor.update_in(cx, |editor, window, cx| {
 9004        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9005            s.select_display_ranges([
 9006                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9007            ]);
 9008        });
 9009    });
 9010    editor.update(cx, |editor, cx| {
 9011        assert_text_with_selections(
 9012            editor,
 9013            indoc! {r#"
 9014                let a = {
 9015                    key: "value",
 9016                };ˇ
 9017            "#},
 9018            cx,
 9019        );
 9020    });
 9021    editor.update_in(cx, |editor, window, cx| {
 9022        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9023    });
 9024    editor.update(cx, |editor, cx| {
 9025        assert_text_with_selections(
 9026            editor,
 9027            indoc! {r#"
 9028                «ˇlet a = {
 9029                    key: "value",
 9030                };
 9031                »"#},
 9032            cx,
 9033        );
 9034    });
 9035}
 9036
 9037#[gpui::test]
 9038async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9039    init_test(cx, |_| {});
 9040
 9041    let language = Arc::new(Language::new(
 9042        LanguageConfig::default(),
 9043        Some(tree_sitter_rust::LANGUAGE.into()),
 9044    ));
 9045
 9046    let text = r#"
 9047        use mod1::mod2::{mod3, mod4};
 9048
 9049        fn fn_1(param1: bool, param2: &str) {
 9050            let var1 = "hello world";
 9051        }
 9052    "#
 9053    .unindent();
 9054
 9055    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9056    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9057    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9058
 9059    editor
 9060        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9061        .await;
 9062
 9063    // Test 1: Cursor on a letter of a string word
 9064    editor.update_in(cx, |editor, window, cx| {
 9065        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9066            s.select_display_ranges([
 9067                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9068            ]);
 9069        });
 9070    });
 9071    editor.update_in(cx, |editor, window, cx| {
 9072        assert_text_with_selections(
 9073            editor,
 9074            indoc! {r#"
 9075                use mod1::mod2::{mod3, mod4};
 9076
 9077                fn fn_1(param1: bool, param2: &str) {
 9078                    let var1 = "hˇello world";
 9079                }
 9080            "#},
 9081            cx,
 9082        );
 9083        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9084        assert_text_with_selections(
 9085            editor,
 9086            indoc! {r#"
 9087                use mod1::mod2::{mod3, mod4};
 9088
 9089                fn fn_1(param1: bool, param2: &str) {
 9090                    let var1 = "«ˇhello» world";
 9091                }
 9092            "#},
 9093            cx,
 9094        );
 9095    });
 9096
 9097    // Test 2: Partial selection within a word
 9098    editor.update_in(cx, |editor, window, cx| {
 9099        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9100            s.select_display_ranges([
 9101                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9102            ]);
 9103        });
 9104    });
 9105    editor.update_in(cx, |editor, window, cx| {
 9106        assert_text_with_selections(
 9107            editor,
 9108            indoc! {r#"
 9109                use mod1::mod2::{mod3, mod4};
 9110
 9111                fn fn_1(param1: bool, param2: &str) {
 9112                    let var1 = "h«elˇ»lo world";
 9113                }
 9114            "#},
 9115            cx,
 9116        );
 9117        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9118        assert_text_with_selections(
 9119            editor,
 9120            indoc! {r#"
 9121                use mod1::mod2::{mod3, mod4};
 9122
 9123                fn fn_1(param1: bool, param2: &str) {
 9124                    let var1 = "«ˇhello» world";
 9125                }
 9126            "#},
 9127            cx,
 9128        );
 9129    });
 9130
 9131    // Test 3: Complete word already selected
 9132    editor.update_in(cx, |editor, window, cx| {
 9133        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9134            s.select_display_ranges([
 9135                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9136            ]);
 9137        });
 9138    });
 9139    editor.update_in(cx, |editor, window, cx| {
 9140        assert_text_with_selections(
 9141            editor,
 9142            indoc! {r#"
 9143                use mod1::mod2::{mod3, mod4};
 9144
 9145                fn fn_1(param1: bool, param2: &str) {
 9146                    let var1 = "«helloˇ» world";
 9147                }
 9148            "#},
 9149            cx,
 9150        );
 9151        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9152        assert_text_with_selections(
 9153            editor,
 9154            indoc! {r#"
 9155                use mod1::mod2::{mod3, mod4};
 9156
 9157                fn fn_1(param1: bool, param2: &str) {
 9158                    let var1 = "«hello worldˇ»";
 9159                }
 9160            "#},
 9161            cx,
 9162        );
 9163    });
 9164
 9165    // Test 4: Selection spanning across words
 9166    editor.update_in(cx, |editor, window, cx| {
 9167        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9168            s.select_display_ranges([
 9169                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9170            ]);
 9171        });
 9172    });
 9173    editor.update_in(cx, |editor, window, cx| {
 9174        assert_text_with_selections(
 9175            editor,
 9176            indoc! {r#"
 9177                use mod1::mod2::{mod3, mod4};
 9178
 9179                fn fn_1(param1: bool, param2: &str) {
 9180                    let var1 = "hel«lo woˇ»rld";
 9181                }
 9182            "#},
 9183            cx,
 9184        );
 9185        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9186        assert_text_with_selections(
 9187            editor,
 9188            indoc! {r#"
 9189                use mod1::mod2::{mod3, mod4};
 9190
 9191                fn fn_1(param1: bool, param2: &str) {
 9192                    let var1 = "«ˇhello world»";
 9193                }
 9194            "#},
 9195            cx,
 9196        );
 9197    });
 9198
 9199    // Test 5: Expansion beyond string
 9200    editor.update_in(cx, |editor, window, cx| {
 9201        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9202        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9203        assert_text_with_selections(
 9204            editor,
 9205            indoc! {r#"
 9206                use mod1::mod2::{mod3, mod4};
 9207
 9208                fn fn_1(param1: bool, param2: &str) {
 9209                    «ˇlet var1 = "hello world";»
 9210                }
 9211            "#},
 9212            cx,
 9213        );
 9214    });
 9215}
 9216
 9217#[gpui::test]
 9218async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9219    init_test(cx, |_| {});
 9220
 9221    let mut cx = EditorTestContext::new(cx).await;
 9222
 9223    let language = Arc::new(Language::new(
 9224        LanguageConfig::default(),
 9225        Some(tree_sitter_rust::LANGUAGE.into()),
 9226    ));
 9227
 9228    cx.update_buffer(|buffer, cx| {
 9229        buffer.set_language(Some(language), cx);
 9230    });
 9231
 9232    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9233    cx.update_editor(|editor, window, cx| {
 9234        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9235    });
 9236
 9237    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9238
 9239    cx.set_state(indoc! { r#"fn a() {
 9240          // what
 9241          // a
 9242          // ˇlong
 9243          // method
 9244          // I
 9245          // sure
 9246          // hope
 9247          // it
 9248          // works
 9249    }"# });
 9250
 9251    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9252    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9253    cx.update(|_, cx| {
 9254        multi_buffer.update(cx, |multi_buffer, cx| {
 9255            multi_buffer.set_excerpts_for_path(
 9256                PathKey::for_buffer(&buffer, cx),
 9257                buffer,
 9258                [Point::new(1, 0)..Point::new(1, 0)],
 9259                3,
 9260                cx,
 9261            );
 9262        });
 9263    });
 9264
 9265    let editor2 = cx.new_window_entity(|window, cx| {
 9266        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9267    });
 9268
 9269    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9270    cx.update_editor(|editor, window, cx| {
 9271        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9272            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9273        })
 9274    });
 9275
 9276    cx.assert_editor_state(indoc! { "
 9277        fn a() {
 9278              // what
 9279              // a
 9280        ˇ      // long
 9281              // method"});
 9282
 9283    cx.update_editor(|editor, window, cx| {
 9284        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9285    });
 9286
 9287    // Although we could potentially make the action work when the syntax node
 9288    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9289    // did. Maybe we could also expand the excerpt to contain the range?
 9290    cx.assert_editor_state(indoc! { "
 9291        fn a() {
 9292              // what
 9293              // a
 9294        ˇ      // long
 9295              // method"});
 9296}
 9297
 9298#[gpui::test]
 9299async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9300    init_test(cx, |_| {});
 9301
 9302    let base_text = r#"
 9303        impl A {
 9304            // this is an uncommitted comment
 9305
 9306            fn b() {
 9307                c();
 9308            }
 9309
 9310            // this is another uncommitted comment
 9311
 9312            fn d() {
 9313                // e
 9314                // f
 9315            }
 9316        }
 9317
 9318        fn g() {
 9319            // h
 9320        }
 9321    "#
 9322    .unindent();
 9323
 9324    let text = r#"
 9325        ˇimpl A {
 9326
 9327            fn b() {
 9328                c();
 9329            }
 9330
 9331            fn d() {
 9332                // e
 9333                // f
 9334            }
 9335        }
 9336
 9337        fn g() {
 9338            // h
 9339        }
 9340    "#
 9341    .unindent();
 9342
 9343    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9344    cx.set_state(&text);
 9345    cx.set_head_text(&base_text);
 9346    cx.update_editor(|editor, window, cx| {
 9347        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9348    });
 9349
 9350    cx.assert_state_with_diff(
 9351        "
 9352        ˇimpl A {
 9353      -     // this is an uncommitted comment
 9354
 9355            fn b() {
 9356                c();
 9357            }
 9358
 9359      -     // this is another uncommitted comment
 9360      -
 9361            fn d() {
 9362                // e
 9363                // f
 9364            }
 9365        }
 9366
 9367        fn g() {
 9368            // h
 9369        }
 9370    "
 9371        .unindent(),
 9372    );
 9373
 9374    let expected_display_text = "
 9375        impl A {
 9376            // this is an uncommitted comment
 9377
 9378            fn b() {
 9379 9380            }
 9381
 9382            // this is another uncommitted comment
 9383
 9384            fn d() {
 9385 9386            }
 9387        }
 9388
 9389        fn g() {
 9390 9391        }
 9392        "
 9393    .unindent();
 9394
 9395    cx.update_editor(|editor, window, cx| {
 9396        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9397        assert_eq!(editor.display_text(cx), expected_display_text);
 9398    });
 9399}
 9400
 9401#[gpui::test]
 9402async fn test_autoindent(cx: &mut TestAppContext) {
 9403    init_test(cx, |_| {});
 9404
 9405    let language = Arc::new(
 9406        Language::new(
 9407            LanguageConfig {
 9408                brackets: BracketPairConfig {
 9409                    pairs: vec![
 9410                        BracketPair {
 9411                            start: "{".to_string(),
 9412                            end: "}".to_string(),
 9413                            close: false,
 9414                            surround: false,
 9415                            newline: true,
 9416                        },
 9417                        BracketPair {
 9418                            start: "(".to_string(),
 9419                            end: ")".to_string(),
 9420                            close: false,
 9421                            surround: false,
 9422                            newline: true,
 9423                        },
 9424                    ],
 9425                    ..Default::default()
 9426                },
 9427                ..Default::default()
 9428            },
 9429            Some(tree_sitter_rust::LANGUAGE.into()),
 9430        )
 9431        .with_indents_query(
 9432            r#"
 9433                (_ "(" ")" @end) @indent
 9434                (_ "{" "}" @end) @indent
 9435            "#,
 9436        )
 9437        .unwrap(),
 9438    );
 9439
 9440    let text = "fn a() {}";
 9441
 9442    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9443    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9444    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9445    editor
 9446        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9447        .await;
 9448
 9449    editor.update_in(cx, |editor, window, cx| {
 9450        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9451            s.select_ranges([5..5, 8..8, 9..9])
 9452        });
 9453        editor.newline(&Newline, window, cx);
 9454        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9455        assert_eq!(
 9456            editor.selections.ranges(cx),
 9457            &[
 9458                Point::new(1, 4)..Point::new(1, 4),
 9459                Point::new(3, 4)..Point::new(3, 4),
 9460                Point::new(5, 0)..Point::new(5, 0)
 9461            ]
 9462        );
 9463    });
 9464}
 9465
 9466#[gpui::test]
 9467async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9468    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9469
 9470    let language = Arc::new(
 9471        Language::new(
 9472            LanguageConfig {
 9473                brackets: BracketPairConfig {
 9474                    pairs: vec![
 9475                        BracketPair {
 9476                            start: "{".to_string(),
 9477                            end: "}".to_string(),
 9478                            close: false,
 9479                            surround: false,
 9480                            newline: true,
 9481                        },
 9482                        BracketPair {
 9483                            start: "(".to_string(),
 9484                            end: ")".to_string(),
 9485                            close: false,
 9486                            surround: false,
 9487                            newline: true,
 9488                        },
 9489                    ],
 9490                    ..Default::default()
 9491                },
 9492                ..Default::default()
 9493            },
 9494            Some(tree_sitter_rust::LANGUAGE.into()),
 9495        )
 9496        .with_indents_query(
 9497            r#"
 9498                (_ "(" ")" @end) @indent
 9499                (_ "{" "}" @end) @indent
 9500            "#,
 9501        )
 9502        .unwrap(),
 9503    );
 9504
 9505    let text = "fn a() {}";
 9506
 9507    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9508    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9509    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9510    editor
 9511        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9512        .await;
 9513
 9514    editor.update_in(cx, |editor, window, cx| {
 9515        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9516            s.select_ranges([5..5, 8..8, 9..9])
 9517        });
 9518        editor.newline(&Newline, window, cx);
 9519        assert_eq!(
 9520            editor.text(cx),
 9521            indoc!(
 9522                "
 9523                fn a(
 9524
 9525                ) {
 9526
 9527                }
 9528                "
 9529            )
 9530        );
 9531        assert_eq!(
 9532            editor.selections.ranges(cx),
 9533            &[
 9534                Point::new(1, 0)..Point::new(1, 0),
 9535                Point::new(3, 0)..Point::new(3, 0),
 9536                Point::new(5, 0)..Point::new(5, 0)
 9537            ]
 9538        );
 9539    });
 9540}
 9541
 9542#[gpui::test]
 9543async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9544    init_test(cx, |settings| {
 9545        settings.defaults.auto_indent = Some(true);
 9546        settings.languages.0.insert(
 9547            "python".into(),
 9548            LanguageSettingsContent {
 9549                auto_indent: Some(false),
 9550                ..Default::default()
 9551            },
 9552        );
 9553    });
 9554
 9555    let mut cx = EditorTestContext::new(cx).await;
 9556
 9557    let injected_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: true,
 9573                            surround: false,
 9574                            newline: true,
 9575                        },
 9576                    ],
 9577                    ..Default::default()
 9578                },
 9579                name: "python".into(),
 9580                ..Default::default()
 9581            },
 9582            Some(tree_sitter_python::LANGUAGE.into()),
 9583        )
 9584        .with_indents_query(
 9585            r#"
 9586                (_ "(" ")" @end) @indent
 9587                (_ "{" "}" @end) @indent
 9588            "#,
 9589        )
 9590        .unwrap(),
 9591    );
 9592
 9593    let language = Arc::new(
 9594        Language::new(
 9595            LanguageConfig {
 9596                brackets: BracketPairConfig {
 9597                    pairs: vec![
 9598                        BracketPair {
 9599                            start: "{".to_string(),
 9600                            end: "}".to_string(),
 9601                            close: false,
 9602                            surround: false,
 9603                            newline: true,
 9604                        },
 9605                        BracketPair {
 9606                            start: "(".to_string(),
 9607                            end: ")".to_string(),
 9608                            close: true,
 9609                            surround: false,
 9610                            newline: true,
 9611                        },
 9612                    ],
 9613                    ..Default::default()
 9614                },
 9615                name: LanguageName::new("rust"),
 9616                ..Default::default()
 9617            },
 9618            Some(tree_sitter_rust::LANGUAGE.into()),
 9619        )
 9620        .with_indents_query(
 9621            r#"
 9622                (_ "(" ")" @end) @indent
 9623                (_ "{" "}" @end) @indent
 9624            "#,
 9625        )
 9626        .unwrap()
 9627        .with_injection_query(
 9628            r#"
 9629            (macro_invocation
 9630                macro: (identifier) @_macro_name
 9631                (token_tree) @injection.content
 9632                (#set! injection.language "python"))
 9633           "#,
 9634        )
 9635        .unwrap(),
 9636    );
 9637
 9638    cx.language_registry().add(injected_language);
 9639    cx.language_registry().add(language.clone());
 9640
 9641    cx.update_buffer(|buffer, cx| {
 9642        buffer.set_language(Some(language), cx);
 9643    });
 9644
 9645    cx.set_state(r#"struct A {ˇ}"#);
 9646
 9647    cx.update_editor(|editor, window, cx| {
 9648        editor.newline(&Default::default(), window, cx);
 9649    });
 9650
 9651    cx.assert_editor_state(indoc!(
 9652        "struct A {
 9653            ˇ
 9654        }"
 9655    ));
 9656
 9657    cx.set_state(r#"select_biased!(ˇ)"#);
 9658
 9659    cx.update_editor(|editor, window, cx| {
 9660        editor.newline(&Default::default(), window, cx);
 9661        editor.handle_input("def ", window, cx);
 9662        editor.handle_input("(", window, cx);
 9663        editor.newline(&Default::default(), window, cx);
 9664        editor.handle_input("a", window, cx);
 9665    });
 9666
 9667    cx.assert_editor_state(indoc!(
 9668        "select_biased!(
 9669        def (
 9670 9671        )
 9672        )"
 9673    ));
 9674}
 9675
 9676#[gpui::test]
 9677async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9678    init_test(cx, |_| {});
 9679
 9680    {
 9681        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9682        cx.set_state(indoc! {"
 9683            impl A {
 9684
 9685                fn b() {}
 9686
 9687            «fn c() {
 9688
 9689            }ˇ»
 9690            }
 9691        "});
 9692
 9693        cx.update_editor(|editor, window, cx| {
 9694            editor.autoindent(&Default::default(), window, cx);
 9695        });
 9696
 9697        cx.assert_editor_state(indoc! {"
 9698            impl A {
 9699
 9700                fn b() {}
 9701
 9702                «fn c() {
 9703
 9704                }ˇ»
 9705            }
 9706        "});
 9707    }
 9708
 9709    {
 9710        let mut cx = EditorTestContext::new_multibuffer(
 9711            cx,
 9712            [indoc! { "
 9713                impl A {
 9714                «
 9715                // a
 9716                fn b(){}
 9717                »
 9718                «
 9719                    }
 9720                    fn c(){}
 9721                »
 9722            "}],
 9723        );
 9724
 9725        let buffer = cx.update_editor(|editor, _, cx| {
 9726            let buffer = editor.buffer().update(cx, |buffer, _| {
 9727                buffer.all_buffers().iter().next().unwrap().clone()
 9728            });
 9729            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9730            buffer
 9731        });
 9732
 9733        cx.run_until_parked();
 9734        cx.update_editor(|editor, window, cx| {
 9735            editor.select_all(&Default::default(), window, cx);
 9736            editor.autoindent(&Default::default(), window, cx)
 9737        });
 9738        cx.run_until_parked();
 9739
 9740        cx.update(|_, cx| {
 9741            assert_eq!(
 9742                buffer.read(cx).text(),
 9743                indoc! { "
 9744                    impl A {
 9745
 9746                        // a
 9747                        fn b(){}
 9748
 9749
 9750                    }
 9751                    fn c(){}
 9752
 9753                " }
 9754            )
 9755        });
 9756    }
 9757}
 9758
 9759#[gpui::test]
 9760async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9761    init_test(cx, |_| {});
 9762
 9763    let mut cx = EditorTestContext::new(cx).await;
 9764
 9765    let language = Arc::new(Language::new(
 9766        LanguageConfig {
 9767            brackets: BracketPairConfig {
 9768                pairs: vec![
 9769                    BracketPair {
 9770                        start: "{".to_string(),
 9771                        end: "}".to_string(),
 9772                        close: true,
 9773                        surround: true,
 9774                        newline: true,
 9775                    },
 9776                    BracketPair {
 9777                        start: "(".to_string(),
 9778                        end: ")".to_string(),
 9779                        close: true,
 9780                        surround: true,
 9781                        newline: true,
 9782                    },
 9783                    BracketPair {
 9784                        start: "/*".to_string(),
 9785                        end: " */".to_string(),
 9786                        close: true,
 9787                        surround: true,
 9788                        newline: true,
 9789                    },
 9790                    BracketPair {
 9791                        start: "[".to_string(),
 9792                        end: "]".to_string(),
 9793                        close: false,
 9794                        surround: false,
 9795                        newline: true,
 9796                    },
 9797                    BracketPair {
 9798                        start: "\"".to_string(),
 9799                        end: "\"".to_string(),
 9800                        close: true,
 9801                        surround: true,
 9802                        newline: false,
 9803                    },
 9804                    BracketPair {
 9805                        start: "<".to_string(),
 9806                        end: ">".to_string(),
 9807                        close: false,
 9808                        surround: true,
 9809                        newline: true,
 9810                    },
 9811                ],
 9812                ..Default::default()
 9813            },
 9814            autoclose_before: "})]".to_string(),
 9815            ..Default::default()
 9816        },
 9817        Some(tree_sitter_rust::LANGUAGE.into()),
 9818    ));
 9819
 9820    cx.language_registry().add(language.clone());
 9821    cx.update_buffer(|buffer, cx| {
 9822        buffer.set_language(Some(language), cx);
 9823    });
 9824
 9825    cx.set_state(
 9826        &r#"
 9827            🏀ˇ
 9828            εˇ
 9829            ❤️ˇ
 9830        "#
 9831        .unindent(),
 9832    );
 9833
 9834    // autoclose multiple nested brackets at multiple cursors
 9835    cx.update_editor(|editor, window, cx| {
 9836        editor.handle_input("{", window, cx);
 9837        editor.handle_input("{", window, cx);
 9838        editor.handle_input("{", window, cx);
 9839    });
 9840    cx.assert_editor_state(
 9841        &"
 9842            🏀{{{ˇ}}}
 9843            ε{{{ˇ}}}
 9844            ❤️{{{ˇ}}}
 9845        "
 9846        .unindent(),
 9847    );
 9848
 9849    // insert a different closing bracket
 9850    cx.update_editor(|editor, window, cx| {
 9851        editor.handle_input(")", window, cx);
 9852    });
 9853    cx.assert_editor_state(
 9854        &"
 9855            🏀{{{)ˇ}}}
 9856            ε{{{)ˇ}}}
 9857            ❤️{{{)ˇ}}}
 9858        "
 9859        .unindent(),
 9860    );
 9861
 9862    // skip over the auto-closed brackets when typing a closing bracket
 9863    cx.update_editor(|editor, window, cx| {
 9864        editor.move_right(&MoveRight, window, cx);
 9865        editor.handle_input("}", window, cx);
 9866        editor.handle_input("}", window, cx);
 9867        editor.handle_input("}", window, cx);
 9868    });
 9869    cx.assert_editor_state(
 9870        &"
 9871            🏀{{{)}}}}ˇ
 9872            ε{{{)}}}}ˇ
 9873            ❤️{{{)}}}}ˇ
 9874        "
 9875        .unindent(),
 9876    );
 9877
 9878    // autoclose multi-character pairs
 9879    cx.set_state(
 9880        &"
 9881            ˇ
 9882            ˇ
 9883        "
 9884        .unindent(),
 9885    );
 9886    cx.update_editor(|editor, window, cx| {
 9887        editor.handle_input("/", window, cx);
 9888        editor.handle_input("*", window, cx);
 9889    });
 9890    cx.assert_editor_state(
 9891        &"
 9892            /*ˇ */
 9893            /*ˇ */
 9894        "
 9895        .unindent(),
 9896    );
 9897
 9898    // one cursor autocloses a multi-character pair, one cursor
 9899    // does not autoclose.
 9900    cx.set_state(
 9901        &"
 9902 9903            ˇ
 9904        "
 9905        .unindent(),
 9906    );
 9907    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 9908    cx.assert_editor_state(
 9909        &"
 9910            /*ˇ */
 9911 9912        "
 9913        .unindent(),
 9914    );
 9915
 9916    // Don't autoclose if the next character isn't whitespace and isn't
 9917    // listed in the language's "autoclose_before" section.
 9918    cx.set_state("ˇa b");
 9919    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9920    cx.assert_editor_state("{ˇa b");
 9921
 9922    // Don't autoclose if `close` is false for the bracket pair
 9923    cx.set_state("ˇ");
 9924    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 9925    cx.assert_editor_state("");
 9926
 9927    // Surround with brackets if text is selected
 9928    cx.set_state("«aˇ» b");
 9929    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9930    cx.assert_editor_state("{«aˇ»} b");
 9931
 9932    // Autoclose when not immediately after a word character
 9933    cx.set_state("a ˇ");
 9934    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9935    cx.assert_editor_state("a \"ˇ\"");
 9936
 9937    // Autoclose pair where the start and end characters are the same
 9938    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9939    cx.assert_editor_state("a \"\"ˇ");
 9940
 9941    // Don't autoclose when immediately after a word character
 9942    cx.set_state("");
 9943    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9944    cx.assert_editor_state("a\"ˇ");
 9945
 9946    // Do autoclose when after a non-word character
 9947    cx.set_state("");
 9948    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9949    cx.assert_editor_state("{\"ˇ\"");
 9950
 9951    // Non identical pairs autoclose regardless of preceding character
 9952    cx.set_state("");
 9953    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9954    cx.assert_editor_state("a{ˇ}");
 9955
 9956    // Don't autoclose pair if autoclose is disabled
 9957    cx.set_state("ˇ");
 9958    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9959    cx.assert_editor_state("");
 9960
 9961    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 9962    cx.set_state("«aˇ» b");
 9963    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9964    cx.assert_editor_state("<«aˇ»> b");
 9965}
 9966
 9967#[gpui::test]
 9968async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 9969    init_test(cx, |settings| {
 9970        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9971    });
 9972
 9973    let mut cx = EditorTestContext::new(cx).await;
 9974
 9975    let language = Arc::new(Language::new(
 9976        LanguageConfig {
 9977            brackets: BracketPairConfig {
 9978                pairs: vec![
 9979                    BracketPair {
 9980                        start: "{".to_string(),
 9981                        end: "}".to_string(),
 9982                        close: true,
 9983                        surround: true,
 9984                        newline: true,
 9985                    },
 9986                    BracketPair {
 9987                        start: "(".to_string(),
 9988                        end: ")".to_string(),
 9989                        close: true,
 9990                        surround: true,
 9991                        newline: true,
 9992                    },
 9993                    BracketPair {
 9994                        start: "[".to_string(),
 9995                        end: "]".to_string(),
 9996                        close: false,
 9997                        surround: false,
 9998                        newline: true,
 9999                    },
10000                ],
10001                ..Default::default()
10002            },
10003            autoclose_before: "})]".to_string(),
10004            ..Default::default()
10005        },
10006        Some(tree_sitter_rust::LANGUAGE.into()),
10007    ));
10008
10009    cx.language_registry().add(language.clone());
10010    cx.update_buffer(|buffer, cx| {
10011        buffer.set_language(Some(language), cx);
10012    });
10013
10014    cx.set_state(
10015        &"
10016            ˇ
10017            ˇ
10018            ˇ
10019        "
10020        .unindent(),
10021    );
10022
10023    // ensure only matching closing brackets are skipped over
10024    cx.update_editor(|editor, window, cx| {
10025        editor.handle_input("}", window, cx);
10026        editor.move_left(&MoveLeft, window, cx);
10027        editor.handle_input(")", window, cx);
10028        editor.move_left(&MoveLeft, window, cx);
10029    });
10030    cx.assert_editor_state(
10031        &"
10032            ˇ)}
10033            ˇ)}
10034            ˇ)}
10035        "
10036        .unindent(),
10037    );
10038
10039    // skip-over closing brackets at multiple cursors
10040    cx.update_editor(|editor, window, cx| {
10041        editor.handle_input(")", window, cx);
10042        editor.handle_input("}", window, cx);
10043    });
10044    cx.assert_editor_state(
10045        &"
10046            )}ˇ
10047            )}ˇ
10048            )}ˇ
10049        "
10050        .unindent(),
10051    );
10052
10053    // ignore non-close brackets
10054    cx.update_editor(|editor, window, cx| {
10055        editor.handle_input("]", window, cx);
10056        editor.move_left(&MoveLeft, window, cx);
10057        editor.handle_input("]", window, cx);
10058    });
10059    cx.assert_editor_state(
10060        &"
10061            )}]ˇ]
10062            )}]ˇ]
10063            )}]ˇ]
10064        "
10065        .unindent(),
10066    );
10067}
10068
10069#[gpui::test]
10070async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10071    init_test(cx, |_| {});
10072
10073    let mut cx = EditorTestContext::new(cx).await;
10074
10075    let html_language = Arc::new(
10076        Language::new(
10077            LanguageConfig {
10078                name: "HTML".into(),
10079                brackets: BracketPairConfig {
10080                    pairs: vec![
10081                        BracketPair {
10082                            start: "<".into(),
10083                            end: ">".into(),
10084                            close: true,
10085                            ..Default::default()
10086                        },
10087                        BracketPair {
10088                            start: "{".into(),
10089                            end: "}".into(),
10090                            close: true,
10091                            ..Default::default()
10092                        },
10093                        BracketPair {
10094                            start: "(".into(),
10095                            end: ")".into(),
10096                            close: true,
10097                            ..Default::default()
10098                        },
10099                    ],
10100                    ..Default::default()
10101                },
10102                autoclose_before: "})]>".into(),
10103                ..Default::default()
10104            },
10105            Some(tree_sitter_html::LANGUAGE.into()),
10106        )
10107        .with_injection_query(
10108            r#"
10109            (script_element
10110                (raw_text) @injection.content
10111                (#set! injection.language "javascript"))
10112            "#,
10113        )
10114        .unwrap(),
10115    );
10116
10117    let javascript_language = Arc::new(Language::new(
10118        LanguageConfig {
10119            name: "JavaScript".into(),
10120            brackets: BracketPairConfig {
10121                pairs: vec![
10122                    BracketPair {
10123                        start: "/*".into(),
10124                        end: " */".into(),
10125                        close: true,
10126                        ..Default::default()
10127                    },
10128                    BracketPair {
10129                        start: "{".into(),
10130                        end: "}".into(),
10131                        close: true,
10132                        ..Default::default()
10133                    },
10134                    BracketPair {
10135                        start: "(".into(),
10136                        end: ")".into(),
10137                        close: true,
10138                        ..Default::default()
10139                    },
10140                ],
10141                ..Default::default()
10142            },
10143            autoclose_before: "})]>".into(),
10144            ..Default::default()
10145        },
10146        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10147    ));
10148
10149    cx.language_registry().add(html_language.clone());
10150    cx.language_registry().add(javascript_language);
10151    cx.executor().run_until_parked();
10152
10153    cx.update_buffer(|buffer, cx| {
10154        buffer.set_language(Some(html_language), cx);
10155    });
10156
10157    cx.set_state(
10158        &r#"
10159            <body>ˇ
10160                <script>
10161                    var x = 1;ˇ
10162                </script>
10163            </body>ˇ
10164        "#
10165        .unindent(),
10166    );
10167
10168    // Precondition: different languages are active at different locations.
10169    cx.update_editor(|editor, window, cx| {
10170        let snapshot = editor.snapshot(window, cx);
10171        let cursors = editor.selections.ranges::<usize>(cx);
10172        let languages = cursors
10173            .iter()
10174            .map(|c| snapshot.language_at(c.start).unwrap().name())
10175            .collect::<Vec<_>>();
10176        assert_eq!(
10177            languages,
10178            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10179        );
10180    });
10181
10182    // Angle brackets autoclose in HTML, but not JavaScript.
10183    cx.update_editor(|editor, window, cx| {
10184        editor.handle_input("<", window, cx);
10185        editor.handle_input("a", window, cx);
10186    });
10187    cx.assert_editor_state(
10188        &r#"
10189            <body><aˇ>
10190                <script>
10191                    var x = 1;<aˇ
10192                </script>
10193            </body><aˇ>
10194        "#
10195        .unindent(),
10196    );
10197
10198    // Curly braces and parens autoclose in both HTML and JavaScript.
10199    cx.update_editor(|editor, window, cx| {
10200        editor.handle_input(" b=", window, cx);
10201        editor.handle_input("{", window, cx);
10202        editor.handle_input("c", window, cx);
10203        editor.handle_input("(", window, cx);
10204    });
10205    cx.assert_editor_state(
10206        &r#"
10207            <body><a b={c(ˇ)}>
10208                <script>
10209                    var x = 1;<a b={c(ˇ)}
10210                </script>
10211            </body><a b={c(ˇ)}>
10212        "#
10213        .unindent(),
10214    );
10215
10216    // Brackets that were already autoclosed are skipped.
10217    cx.update_editor(|editor, window, cx| {
10218        editor.handle_input(")", window, cx);
10219        editor.handle_input("d", window, cx);
10220        editor.handle_input("}", window, cx);
10221    });
10222    cx.assert_editor_state(
10223        &r#"
10224            <body><a b={c()d}ˇ>
10225                <script>
10226                    var x = 1;<a b={c()d}ˇ
10227                </script>
10228            </body><a b={c()d}ˇ>
10229        "#
10230        .unindent(),
10231    );
10232    cx.update_editor(|editor, window, cx| {
10233        editor.handle_input(">", window, cx);
10234    });
10235    cx.assert_editor_state(
10236        &r#"
10237            <body><a b={c()d}>ˇ
10238                <script>
10239                    var x = 1;<a b={c()d}>ˇ
10240                </script>
10241            </body><a b={c()d}>ˇ
10242        "#
10243        .unindent(),
10244    );
10245
10246    // Reset
10247    cx.set_state(
10248        &r#"
10249            <body>ˇ
10250                <script>
10251                    var x = 1;ˇ
10252                </script>
10253            </body>ˇ
10254        "#
10255        .unindent(),
10256    );
10257
10258    cx.update_editor(|editor, window, cx| {
10259        editor.handle_input("<", window, cx);
10260    });
10261    cx.assert_editor_state(
10262        &r#"
10263            <body><ˇ>
10264                <script>
10265                    var x = 1;<ˇ
10266                </script>
10267            </body><ˇ>
10268        "#
10269        .unindent(),
10270    );
10271
10272    // When backspacing, the closing angle brackets are removed.
10273    cx.update_editor(|editor, window, cx| {
10274        editor.backspace(&Backspace, window, cx);
10275    });
10276    cx.assert_editor_state(
10277        &r#"
10278            <body>ˇ
10279                <script>
10280                    var x = 1;ˇ
10281                </script>
10282            </body>ˇ
10283        "#
10284        .unindent(),
10285    );
10286
10287    // Block comments autoclose in JavaScript, but not HTML.
10288    cx.update_editor(|editor, window, cx| {
10289        editor.handle_input("/", window, cx);
10290        editor.handle_input("*", window, cx);
10291    });
10292    cx.assert_editor_state(
10293        &r#"
10294            <body>/*ˇ
10295                <script>
10296                    var x = 1;/*ˇ */
10297                </script>
10298            </body>/*ˇ
10299        "#
10300        .unindent(),
10301    );
10302}
10303
10304#[gpui::test]
10305async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10306    init_test(cx, |_| {});
10307
10308    let mut cx = EditorTestContext::new(cx).await;
10309
10310    let rust_language = Arc::new(
10311        Language::new(
10312            LanguageConfig {
10313                name: "Rust".into(),
10314                brackets: serde_json::from_value(json!([
10315                    { "start": "{", "end": "}", "close": true, "newline": true },
10316                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10317                ]))
10318                .unwrap(),
10319                autoclose_before: "})]>".into(),
10320                ..Default::default()
10321            },
10322            Some(tree_sitter_rust::LANGUAGE.into()),
10323        )
10324        .with_override_query("(string_literal) @string")
10325        .unwrap(),
10326    );
10327
10328    cx.language_registry().add(rust_language.clone());
10329    cx.update_buffer(|buffer, cx| {
10330        buffer.set_language(Some(rust_language), cx);
10331    });
10332
10333    cx.set_state(
10334        &r#"
10335            let x = ˇ
10336        "#
10337        .unindent(),
10338    );
10339
10340    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10341    cx.update_editor(|editor, window, cx| {
10342        editor.handle_input("\"", window, cx);
10343    });
10344    cx.assert_editor_state(
10345        &r#"
10346            let x = "ˇ"
10347        "#
10348        .unindent(),
10349    );
10350
10351    // Inserting another quotation mark. The cursor moves across the existing
10352    // automatically-inserted quotation mark.
10353    cx.update_editor(|editor, window, cx| {
10354        editor.handle_input("\"", window, cx);
10355    });
10356    cx.assert_editor_state(
10357        &r#"
10358            let x = ""ˇ
10359        "#
10360        .unindent(),
10361    );
10362
10363    // Reset
10364    cx.set_state(
10365        &r#"
10366            let x = ˇ
10367        "#
10368        .unindent(),
10369    );
10370
10371    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10372    cx.update_editor(|editor, window, cx| {
10373        editor.handle_input("\"", window, cx);
10374        editor.handle_input(" ", window, cx);
10375        editor.move_left(&Default::default(), window, cx);
10376        editor.handle_input("\\", window, cx);
10377        editor.handle_input("\"", window, cx);
10378    });
10379    cx.assert_editor_state(
10380        &r#"
10381            let x = "\"ˇ "
10382        "#
10383        .unindent(),
10384    );
10385
10386    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10387    // mark. Nothing is inserted.
10388    cx.update_editor(|editor, window, cx| {
10389        editor.move_right(&Default::default(), window, cx);
10390        editor.handle_input("\"", window, cx);
10391    });
10392    cx.assert_editor_state(
10393        &r#"
10394            let x = "\" "ˇ
10395        "#
10396        .unindent(),
10397    );
10398}
10399
10400#[gpui::test]
10401async fn test_surround_with_pair(cx: &mut TestAppContext) {
10402    init_test(cx, |_| {});
10403
10404    let language = Arc::new(Language::new(
10405        LanguageConfig {
10406            brackets: BracketPairConfig {
10407                pairs: vec![
10408                    BracketPair {
10409                        start: "{".to_string(),
10410                        end: "}".to_string(),
10411                        close: true,
10412                        surround: true,
10413                        newline: true,
10414                    },
10415                    BracketPair {
10416                        start: "/* ".to_string(),
10417                        end: "*/".to_string(),
10418                        close: true,
10419                        surround: true,
10420                        ..Default::default()
10421                    },
10422                ],
10423                ..Default::default()
10424            },
10425            ..Default::default()
10426        },
10427        Some(tree_sitter_rust::LANGUAGE.into()),
10428    ));
10429
10430    let text = r#"
10431        a
10432        b
10433        c
10434    "#
10435    .unindent();
10436
10437    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10438    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10439    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10440    editor
10441        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10442        .await;
10443
10444    editor.update_in(cx, |editor, window, cx| {
10445        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10446            s.select_display_ranges([
10447                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10448                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10449                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10450            ])
10451        });
10452
10453        editor.handle_input("{", window, cx);
10454        editor.handle_input("{", window, cx);
10455        editor.handle_input("{", window, cx);
10456        assert_eq!(
10457            editor.text(cx),
10458            "
10459                {{{a}}}
10460                {{{b}}}
10461                {{{c}}}
10462            "
10463            .unindent()
10464        );
10465        assert_eq!(
10466            editor.selections.display_ranges(cx),
10467            [
10468                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10469                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10470                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10471            ]
10472        );
10473
10474        editor.undo(&Undo, window, cx);
10475        editor.undo(&Undo, window, cx);
10476        editor.undo(&Undo, window, cx);
10477        assert_eq!(
10478            editor.text(cx),
10479            "
10480                a
10481                b
10482                c
10483            "
10484            .unindent()
10485        );
10486        assert_eq!(
10487            editor.selections.display_ranges(cx),
10488            [
10489                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10490                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10491                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10492            ]
10493        );
10494
10495        // Ensure inserting the first character of a multi-byte bracket pair
10496        // doesn't surround the selections with the bracket.
10497        editor.handle_input("/", window, cx);
10498        assert_eq!(
10499            editor.text(cx),
10500            "
10501                /
10502                /
10503                /
10504            "
10505            .unindent()
10506        );
10507        assert_eq!(
10508            editor.selections.display_ranges(cx),
10509            [
10510                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10511                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10512                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10513            ]
10514        );
10515
10516        editor.undo(&Undo, window, cx);
10517        assert_eq!(
10518            editor.text(cx),
10519            "
10520                a
10521                b
10522                c
10523            "
10524            .unindent()
10525        );
10526        assert_eq!(
10527            editor.selections.display_ranges(cx),
10528            [
10529                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10530                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10531                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10532            ]
10533        );
10534
10535        // Ensure inserting the last character of a multi-byte bracket pair
10536        // doesn't surround the selections with the bracket.
10537        editor.handle_input("*", window, cx);
10538        assert_eq!(
10539            editor.text(cx),
10540            "
10541                *
10542                *
10543                *
10544            "
10545            .unindent()
10546        );
10547        assert_eq!(
10548            editor.selections.display_ranges(cx),
10549            [
10550                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10551                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10552                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10553            ]
10554        );
10555    });
10556}
10557
10558#[gpui::test]
10559async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10560    init_test(cx, |_| {});
10561
10562    let language = Arc::new(Language::new(
10563        LanguageConfig {
10564            brackets: BracketPairConfig {
10565                pairs: vec![BracketPair {
10566                    start: "{".to_string(),
10567                    end: "}".to_string(),
10568                    close: true,
10569                    surround: true,
10570                    newline: true,
10571                }],
10572                ..Default::default()
10573            },
10574            autoclose_before: "}".to_string(),
10575            ..Default::default()
10576        },
10577        Some(tree_sitter_rust::LANGUAGE.into()),
10578    ));
10579
10580    let text = r#"
10581        a
10582        b
10583        c
10584    "#
10585    .unindent();
10586
10587    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10588    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10589    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10590    editor
10591        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10592        .await;
10593
10594    editor.update_in(cx, |editor, window, cx| {
10595        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10596            s.select_ranges([
10597                Point::new(0, 1)..Point::new(0, 1),
10598                Point::new(1, 1)..Point::new(1, 1),
10599                Point::new(2, 1)..Point::new(2, 1),
10600            ])
10601        });
10602
10603        editor.handle_input("{", window, cx);
10604        editor.handle_input("{", window, cx);
10605        editor.handle_input("_", window, cx);
10606        assert_eq!(
10607            editor.text(cx),
10608            "
10609                a{{_}}
10610                b{{_}}
10611                c{{_}}
10612            "
10613            .unindent()
10614        );
10615        assert_eq!(
10616            editor.selections.ranges::<Point>(cx),
10617            [
10618                Point::new(0, 4)..Point::new(0, 4),
10619                Point::new(1, 4)..Point::new(1, 4),
10620                Point::new(2, 4)..Point::new(2, 4)
10621            ]
10622        );
10623
10624        editor.backspace(&Default::default(), window, cx);
10625        editor.backspace(&Default::default(), window, cx);
10626        assert_eq!(
10627            editor.text(cx),
10628            "
10629                a{}
10630                b{}
10631                c{}
10632            "
10633            .unindent()
10634        );
10635        assert_eq!(
10636            editor.selections.ranges::<Point>(cx),
10637            [
10638                Point::new(0, 2)..Point::new(0, 2),
10639                Point::new(1, 2)..Point::new(1, 2),
10640                Point::new(2, 2)..Point::new(2, 2)
10641            ]
10642        );
10643
10644        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10645        assert_eq!(
10646            editor.text(cx),
10647            "
10648                a
10649                b
10650                c
10651            "
10652            .unindent()
10653        );
10654        assert_eq!(
10655            editor.selections.ranges::<Point>(cx),
10656            [
10657                Point::new(0, 1)..Point::new(0, 1),
10658                Point::new(1, 1)..Point::new(1, 1),
10659                Point::new(2, 1)..Point::new(2, 1)
10660            ]
10661        );
10662    });
10663}
10664
10665#[gpui::test]
10666async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10667    init_test(cx, |settings| {
10668        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10669    });
10670
10671    let mut cx = EditorTestContext::new(cx).await;
10672
10673    let language = Arc::new(Language::new(
10674        LanguageConfig {
10675            brackets: BracketPairConfig {
10676                pairs: vec![
10677                    BracketPair {
10678                        start: "{".to_string(),
10679                        end: "}".to_string(),
10680                        close: true,
10681                        surround: true,
10682                        newline: true,
10683                    },
10684                    BracketPair {
10685                        start: "(".to_string(),
10686                        end: ")".to_string(),
10687                        close: true,
10688                        surround: true,
10689                        newline: true,
10690                    },
10691                    BracketPair {
10692                        start: "[".to_string(),
10693                        end: "]".to_string(),
10694                        close: false,
10695                        surround: true,
10696                        newline: true,
10697                    },
10698                ],
10699                ..Default::default()
10700            },
10701            autoclose_before: "})]".to_string(),
10702            ..Default::default()
10703        },
10704        Some(tree_sitter_rust::LANGUAGE.into()),
10705    ));
10706
10707    cx.language_registry().add(language.clone());
10708    cx.update_buffer(|buffer, cx| {
10709        buffer.set_language(Some(language), cx);
10710    });
10711
10712    cx.set_state(
10713        &"
10714            {(ˇ)}
10715            [[ˇ]]
10716            {(ˇ)}
10717        "
10718        .unindent(),
10719    );
10720
10721    cx.update_editor(|editor, window, cx| {
10722        editor.backspace(&Default::default(), window, cx);
10723        editor.backspace(&Default::default(), window, cx);
10724    });
10725
10726    cx.assert_editor_state(
10727        &"
10728            ˇ
10729            ˇ]]
10730            ˇ
10731        "
10732        .unindent(),
10733    );
10734
10735    cx.update_editor(|editor, window, cx| {
10736        editor.handle_input("{", window, cx);
10737        editor.handle_input("{", window, cx);
10738        editor.move_right(&MoveRight, window, cx);
10739        editor.move_right(&MoveRight, window, cx);
10740        editor.move_left(&MoveLeft, window, cx);
10741        editor.move_left(&MoveLeft, window, cx);
10742        editor.backspace(&Default::default(), window, cx);
10743    });
10744
10745    cx.assert_editor_state(
10746        &"
10747            {ˇ}
10748            {ˇ}]]
10749            {ˇ}
10750        "
10751        .unindent(),
10752    );
10753
10754    cx.update_editor(|editor, window, cx| {
10755        editor.backspace(&Default::default(), window, cx);
10756    });
10757
10758    cx.assert_editor_state(
10759        &"
10760            ˇ
10761            ˇ]]
10762            ˇ
10763        "
10764        .unindent(),
10765    );
10766}
10767
10768#[gpui::test]
10769async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10770    init_test(cx, |_| {});
10771
10772    let language = Arc::new(Language::new(
10773        LanguageConfig::default(),
10774        Some(tree_sitter_rust::LANGUAGE.into()),
10775    ));
10776
10777    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10778    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10779    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10780    editor
10781        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10782        .await;
10783
10784    editor.update_in(cx, |editor, window, cx| {
10785        editor.set_auto_replace_emoji_shortcode(true);
10786
10787        editor.handle_input("Hello ", window, cx);
10788        editor.handle_input(":wave", window, cx);
10789        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10790
10791        editor.handle_input(":", window, cx);
10792        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10793
10794        editor.handle_input(" :smile", window, cx);
10795        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10796
10797        editor.handle_input(":", window, cx);
10798        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10799
10800        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10801        editor.handle_input(":wave", window, cx);
10802        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10803
10804        editor.handle_input(":", window, cx);
10805        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10806
10807        editor.handle_input(":1", window, cx);
10808        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10809
10810        editor.handle_input(":", window, cx);
10811        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
10812
10813        // Ensure shortcode does not get replaced when it is part of a word
10814        editor.handle_input(" Test:wave", window, cx);
10815        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
10816
10817        editor.handle_input(":", window, cx);
10818        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
10819
10820        editor.set_auto_replace_emoji_shortcode(false);
10821
10822        // Ensure shortcode does not get replaced when auto replace is off
10823        editor.handle_input(" :wave", window, cx);
10824        assert_eq!(
10825            editor.text(cx),
10826            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
10827        );
10828
10829        editor.handle_input(":", window, cx);
10830        assert_eq!(
10831            editor.text(cx),
10832            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
10833        );
10834    });
10835}
10836
10837#[gpui::test]
10838async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
10839    init_test(cx, |_| {});
10840
10841    let (text, insertion_ranges) = marked_text_ranges(
10842        indoc! {"
10843            ˇ
10844        "},
10845        false,
10846    );
10847
10848    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
10849    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10850
10851    _ = editor.update_in(cx, |editor, window, cx| {
10852        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
10853
10854        editor
10855            .insert_snippet(&insertion_ranges, snippet, window, cx)
10856            .unwrap();
10857
10858        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
10859            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
10860            assert_eq!(editor.text(cx), expected_text);
10861            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
10862        }
10863
10864        assert(
10865            editor,
10866            cx,
10867            indoc! {"
10868            type «» =•
10869            "},
10870        );
10871
10872        assert!(editor.context_menu_visible(), "There should be a matches");
10873    });
10874}
10875
10876#[gpui::test]
10877async fn test_snippets(cx: &mut TestAppContext) {
10878    init_test(cx, |_| {});
10879
10880    let mut cx = EditorTestContext::new(cx).await;
10881
10882    cx.set_state(indoc! {"
10883        a.ˇ b
10884        a.ˇ b
10885        a.ˇ b
10886    "});
10887
10888    cx.update_editor(|editor, window, cx| {
10889        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
10890        let insertion_ranges = editor
10891            .selections
10892            .all(cx)
10893            .iter()
10894            .map(|s| s.range())
10895            .collect::<Vec<_>>();
10896        editor
10897            .insert_snippet(&insertion_ranges, snippet, window, cx)
10898            .unwrap();
10899    });
10900
10901    cx.assert_editor_state(indoc! {"
10902        a.f(«oneˇ», two, «threeˇ») b
10903        a.f(«oneˇ», two, «threeˇ») b
10904        a.f(«oneˇ», two, «threeˇ») b
10905    "});
10906
10907    // Can't move earlier than the first tab stop
10908    cx.update_editor(|editor, window, cx| {
10909        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10910    });
10911    cx.assert_editor_state(indoc! {"
10912        a.f(«oneˇ», two, «threeˇ») b
10913        a.f(«oneˇ», two, «threeˇ») b
10914        a.f(«oneˇ», two, «threeˇ») b
10915    "});
10916
10917    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10918    cx.assert_editor_state(indoc! {"
10919        a.f(one, «twoˇ», three) b
10920        a.f(one, «twoˇ», three) b
10921        a.f(one, «twoˇ», three) b
10922    "});
10923
10924    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
10925    cx.assert_editor_state(indoc! {"
10926        a.f(«oneˇ», two, «threeˇ») b
10927        a.f(«oneˇ», two, «threeˇ») b
10928        a.f(«oneˇ», two, «threeˇ») b
10929    "});
10930
10931    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10932    cx.assert_editor_state(indoc! {"
10933        a.f(one, «twoˇ», three) b
10934        a.f(one, «twoˇ», three) b
10935        a.f(one, «twoˇ», three) b
10936    "});
10937    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10938    cx.assert_editor_state(indoc! {"
10939        a.f(one, two, three)ˇ b
10940        a.f(one, two, three)ˇ b
10941        a.f(one, two, three)ˇ b
10942    "});
10943
10944    // As soon as the last tab stop is reached, snippet state is gone
10945    cx.update_editor(|editor, window, cx| {
10946        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10947    });
10948    cx.assert_editor_state(indoc! {"
10949        a.f(one, two, three)ˇ b
10950        a.f(one, two, three)ˇ b
10951        a.f(one, two, three)ˇ b
10952    "});
10953}
10954
10955#[gpui::test]
10956async fn test_snippet_indentation(cx: &mut TestAppContext) {
10957    init_test(cx, |_| {});
10958
10959    let mut cx = EditorTestContext::new(cx).await;
10960
10961    cx.update_editor(|editor, window, cx| {
10962        let snippet = Snippet::parse(indoc! {"
10963            /*
10964             * Multiline comment with leading indentation
10965             *
10966             * $1
10967             */
10968            $0"})
10969        .unwrap();
10970        let insertion_ranges = editor
10971            .selections
10972            .all(cx)
10973            .iter()
10974            .map(|s| s.range())
10975            .collect::<Vec<_>>();
10976        editor
10977            .insert_snippet(&insertion_ranges, snippet, window, cx)
10978            .unwrap();
10979    });
10980
10981    cx.assert_editor_state(indoc! {"
10982        /*
10983         * Multiline comment with leading indentation
10984         *
10985         * ˇ
10986         */
10987    "});
10988
10989    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10990    cx.assert_editor_state(indoc! {"
10991        /*
10992         * Multiline comment with leading indentation
10993         *
10994         *•
10995         */
10996        ˇ"});
10997}
10998
10999#[gpui::test]
11000async fn test_document_format_during_save(cx: &mut TestAppContext) {
11001    init_test(cx, |_| {});
11002
11003    let fs = FakeFs::new(cx.executor());
11004    fs.insert_file(path!("/file.rs"), Default::default()).await;
11005
11006    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11007
11008    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11009    language_registry.add(rust_lang());
11010    let mut fake_servers = language_registry.register_fake_lsp(
11011        "Rust",
11012        FakeLspAdapter {
11013            capabilities: lsp::ServerCapabilities {
11014                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11015                ..Default::default()
11016            },
11017            ..Default::default()
11018        },
11019    );
11020
11021    let buffer = project
11022        .update(cx, |project, cx| {
11023            project.open_local_buffer(path!("/file.rs"), cx)
11024        })
11025        .await
11026        .unwrap();
11027
11028    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11029    let (editor, cx) = cx.add_window_view(|window, cx| {
11030        build_editor_with_project(project.clone(), buffer, window, cx)
11031    });
11032    editor.update_in(cx, |editor, window, cx| {
11033        editor.set_text("one\ntwo\nthree\n", window, cx)
11034    });
11035    assert!(cx.read(|cx| editor.is_dirty(cx)));
11036
11037    cx.executor().start_waiting();
11038    let fake_server = fake_servers.next().await.unwrap();
11039
11040    {
11041        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11042            move |params, _| async move {
11043                assert_eq!(
11044                    params.text_document.uri,
11045                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11046                );
11047                assert_eq!(params.options.tab_size, 4);
11048                Ok(Some(vec![lsp::TextEdit::new(
11049                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11050                    ", ".to_string(),
11051                )]))
11052            },
11053        );
11054        let save = editor
11055            .update_in(cx, |editor, window, cx| {
11056                editor.save(
11057                    SaveOptions {
11058                        format: true,
11059                        autosave: false,
11060                    },
11061                    project.clone(),
11062                    window,
11063                    cx,
11064                )
11065            })
11066            .unwrap();
11067        cx.executor().start_waiting();
11068        save.await;
11069
11070        assert_eq!(
11071            editor.update(cx, |editor, cx| editor.text(cx)),
11072            "one, two\nthree\n"
11073        );
11074        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11075    }
11076
11077    {
11078        editor.update_in(cx, |editor, window, cx| {
11079            editor.set_text("one\ntwo\nthree\n", window, cx)
11080        });
11081        assert!(cx.read(|cx| editor.is_dirty(cx)));
11082
11083        // Ensure we can still save even if formatting hangs.
11084        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11085            move |params, _| async move {
11086                assert_eq!(
11087                    params.text_document.uri,
11088                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11089                );
11090                futures::future::pending::<()>().await;
11091                unreachable!()
11092            },
11093        );
11094        let save = editor
11095            .update_in(cx, |editor, window, cx| {
11096                editor.save(
11097                    SaveOptions {
11098                        format: true,
11099                        autosave: false,
11100                    },
11101                    project.clone(),
11102                    window,
11103                    cx,
11104                )
11105            })
11106            .unwrap();
11107        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11108        cx.executor().start_waiting();
11109        save.await;
11110        assert_eq!(
11111            editor.update(cx, |editor, cx| editor.text(cx)),
11112            "one\ntwo\nthree\n"
11113        );
11114    }
11115
11116    // Set rust language override and assert overridden tabsize is sent to language server
11117    update_test_language_settings(cx, |settings| {
11118        settings.languages.0.insert(
11119            "Rust".into(),
11120            LanguageSettingsContent {
11121                tab_size: NonZeroU32::new(8),
11122                ..Default::default()
11123            },
11124        );
11125    });
11126
11127    {
11128        editor.update_in(cx, |editor, window, cx| {
11129            editor.set_text("somehting_new\n", window, cx)
11130        });
11131        assert!(cx.read(|cx| editor.is_dirty(cx)));
11132        let _formatting_request_signal = fake_server
11133            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11134                assert_eq!(
11135                    params.text_document.uri,
11136                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11137                );
11138                assert_eq!(params.options.tab_size, 8);
11139                Ok(Some(vec![]))
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}
11158
11159#[gpui::test]
11160async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11161    init_test(cx, |settings| {
11162        settings.defaults.ensure_final_newline_on_save = Some(false);
11163    });
11164
11165    let fs = FakeFs::new(cx.executor());
11166    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11167
11168    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11169
11170    let buffer = project
11171        .update(cx, |project, cx| {
11172            project.open_local_buffer(path!("/file.txt"), cx)
11173        })
11174        .await
11175        .unwrap();
11176
11177    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11178    let (editor, cx) = cx.add_window_view(|window, cx| {
11179        build_editor_with_project(project.clone(), buffer, window, cx)
11180    });
11181    editor.update_in(cx, |editor, window, cx| {
11182        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11183            s.select_ranges([0..0])
11184        });
11185    });
11186    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11187
11188    editor.update_in(cx, |editor, window, cx| {
11189        editor.handle_input("\n", window, cx)
11190    });
11191    cx.run_until_parked();
11192    save(&editor, &project, cx).await;
11193    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11194
11195    editor.update_in(cx, |editor, window, cx| {
11196        editor.undo(&Default::default(), window, cx);
11197    });
11198    save(&editor, &project, cx).await;
11199    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11200
11201    editor.update_in(cx, |editor, window, cx| {
11202        editor.redo(&Default::default(), window, cx);
11203    });
11204    cx.run_until_parked();
11205    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11206
11207    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11208        let save = editor
11209            .update_in(cx, |editor, window, cx| {
11210                editor.save(
11211                    SaveOptions {
11212                        format: true,
11213                        autosave: false,
11214                    },
11215                    project.clone(),
11216                    window,
11217                    cx,
11218                )
11219            })
11220            .unwrap();
11221        cx.executor().start_waiting();
11222        save.await;
11223        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11224    }
11225}
11226
11227#[gpui::test]
11228async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11229    init_test(cx, |_| {});
11230
11231    let cols = 4;
11232    let rows = 10;
11233    let sample_text_1 = sample_text(rows, cols, 'a');
11234    assert_eq!(
11235        sample_text_1,
11236        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11237    );
11238    let sample_text_2 = sample_text(rows, cols, 'l');
11239    assert_eq!(
11240        sample_text_2,
11241        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11242    );
11243    let sample_text_3 = sample_text(rows, cols, 'v');
11244    assert_eq!(
11245        sample_text_3,
11246        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11247    );
11248
11249    let fs = FakeFs::new(cx.executor());
11250    fs.insert_tree(
11251        path!("/a"),
11252        json!({
11253            "main.rs": sample_text_1,
11254            "other.rs": sample_text_2,
11255            "lib.rs": sample_text_3,
11256        }),
11257    )
11258    .await;
11259
11260    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11261    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11262    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11263
11264    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11265    language_registry.add(rust_lang());
11266    let mut fake_servers = language_registry.register_fake_lsp(
11267        "Rust",
11268        FakeLspAdapter {
11269            capabilities: lsp::ServerCapabilities {
11270                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11271                ..Default::default()
11272            },
11273            ..Default::default()
11274        },
11275    );
11276
11277    let worktree = project.update(cx, |project, cx| {
11278        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11279        assert_eq!(worktrees.len(), 1);
11280        worktrees.pop().unwrap()
11281    });
11282    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11283
11284    let buffer_1 = project
11285        .update(cx, |project, cx| {
11286            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11287        })
11288        .await
11289        .unwrap();
11290    let buffer_2 = project
11291        .update(cx, |project, cx| {
11292            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11293        })
11294        .await
11295        .unwrap();
11296    let buffer_3 = project
11297        .update(cx, |project, cx| {
11298            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11299        })
11300        .await
11301        .unwrap();
11302
11303    let multi_buffer = cx.new(|cx| {
11304        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11305        multi_buffer.push_excerpts(
11306            buffer_1.clone(),
11307            [
11308                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11309                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11310                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11311            ],
11312            cx,
11313        );
11314        multi_buffer.push_excerpts(
11315            buffer_2.clone(),
11316            [
11317                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11318                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11319                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11320            ],
11321            cx,
11322        );
11323        multi_buffer.push_excerpts(
11324            buffer_3.clone(),
11325            [
11326                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11327                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11328                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11329            ],
11330            cx,
11331        );
11332        multi_buffer
11333    });
11334    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11335        Editor::new(
11336            EditorMode::full(),
11337            multi_buffer,
11338            Some(project.clone()),
11339            window,
11340            cx,
11341        )
11342    });
11343
11344    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11345        editor.change_selections(
11346            SelectionEffects::scroll(Autoscroll::Next),
11347            window,
11348            cx,
11349            |s| s.select_ranges(Some(1..2)),
11350        );
11351        editor.insert("|one|two|three|", window, cx);
11352    });
11353    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11354    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11355        editor.change_selections(
11356            SelectionEffects::scroll(Autoscroll::Next),
11357            window,
11358            cx,
11359            |s| s.select_ranges(Some(60..70)),
11360        );
11361        editor.insert("|four|five|six|", window, cx);
11362    });
11363    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11364
11365    // First two buffers should be edited, but not the third one.
11366    assert_eq!(
11367        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11368        "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}",
11369    );
11370    buffer_1.update(cx, |buffer, _| {
11371        assert!(buffer.is_dirty());
11372        assert_eq!(
11373            buffer.text(),
11374            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11375        )
11376    });
11377    buffer_2.update(cx, |buffer, _| {
11378        assert!(buffer.is_dirty());
11379        assert_eq!(
11380            buffer.text(),
11381            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11382        )
11383    });
11384    buffer_3.update(cx, |buffer, _| {
11385        assert!(!buffer.is_dirty());
11386        assert_eq!(buffer.text(), sample_text_3,)
11387    });
11388    cx.executor().run_until_parked();
11389
11390    cx.executor().start_waiting();
11391    let save = multi_buffer_editor
11392        .update_in(cx, |editor, window, cx| {
11393            editor.save(
11394                SaveOptions {
11395                    format: true,
11396                    autosave: false,
11397                },
11398                project.clone(),
11399                window,
11400                cx,
11401            )
11402        })
11403        .unwrap();
11404
11405    let fake_server = fake_servers.next().await.unwrap();
11406    fake_server
11407        .server
11408        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11409            Ok(Some(vec![lsp::TextEdit::new(
11410                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11411                format!("[{} formatted]", params.text_document.uri),
11412            )]))
11413        })
11414        .detach();
11415    save.await;
11416
11417    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11418    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11419    assert_eq!(
11420        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11421        uri!(
11422            "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}"
11423        ),
11424    );
11425    buffer_1.update(cx, |buffer, _| {
11426        assert!(!buffer.is_dirty());
11427        assert_eq!(
11428            buffer.text(),
11429            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11430        )
11431    });
11432    buffer_2.update(cx, |buffer, _| {
11433        assert!(!buffer.is_dirty());
11434        assert_eq!(
11435            buffer.text(),
11436            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11437        )
11438    });
11439    buffer_3.update(cx, |buffer, _| {
11440        assert!(!buffer.is_dirty());
11441        assert_eq!(buffer.text(), sample_text_3,)
11442    });
11443}
11444
11445#[gpui::test]
11446async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11447    init_test(cx, |_| {});
11448
11449    let fs = FakeFs::new(cx.executor());
11450    fs.insert_tree(
11451        path!("/dir"),
11452        json!({
11453            "file1.rs": "fn main() { println!(\"hello\"); }",
11454            "file2.rs": "fn test() { println!(\"test\"); }",
11455            "file3.rs": "fn other() { println!(\"other\"); }\n",
11456        }),
11457    )
11458    .await;
11459
11460    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11461    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11462    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11463
11464    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11465    language_registry.add(rust_lang());
11466
11467    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11468    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11469
11470    // Open three buffers
11471    let buffer_1 = project
11472        .update(cx, |project, cx| {
11473            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
11474        })
11475        .await
11476        .unwrap();
11477    let buffer_2 = project
11478        .update(cx, |project, cx| {
11479            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
11480        })
11481        .await
11482        .unwrap();
11483    let buffer_3 = project
11484        .update(cx, |project, cx| {
11485            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
11486        })
11487        .await
11488        .unwrap();
11489
11490    // Create a multi-buffer with all three buffers
11491    let multi_buffer = cx.new(|cx| {
11492        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11493        multi_buffer.push_excerpts(
11494            buffer_1.clone(),
11495            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11496            cx,
11497        );
11498        multi_buffer.push_excerpts(
11499            buffer_2.clone(),
11500            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11501            cx,
11502        );
11503        multi_buffer.push_excerpts(
11504            buffer_3.clone(),
11505            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11506            cx,
11507        );
11508        multi_buffer
11509    });
11510
11511    let editor = cx.new_window_entity(|window, cx| {
11512        Editor::new(
11513            EditorMode::full(),
11514            multi_buffer,
11515            Some(project.clone()),
11516            window,
11517            cx,
11518        )
11519    });
11520
11521    // Edit only the first buffer
11522    editor.update_in(cx, |editor, window, cx| {
11523        editor.change_selections(
11524            SelectionEffects::scroll(Autoscroll::Next),
11525            window,
11526            cx,
11527            |s| s.select_ranges(Some(10..10)),
11528        );
11529        editor.insert("// edited", window, cx);
11530    });
11531
11532    // Verify that only buffer 1 is dirty
11533    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11534    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11535    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11536
11537    // Get write counts after file creation (files were created with initial content)
11538    // We expect each file to have been written once during creation
11539    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11540    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11541    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11542
11543    // Perform autosave
11544    let save_task = editor.update_in(cx, |editor, window, cx| {
11545        editor.save(
11546            SaveOptions {
11547                format: true,
11548                autosave: true,
11549            },
11550            project.clone(),
11551            window,
11552            cx,
11553        )
11554    });
11555    save_task.await.unwrap();
11556
11557    // Only the dirty buffer should have been saved
11558    assert_eq!(
11559        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11560        1,
11561        "Buffer 1 was dirty, so it should have been written once during autosave"
11562    );
11563    assert_eq!(
11564        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11565        0,
11566        "Buffer 2 was clean, so it should not have been written during autosave"
11567    );
11568    assert_eq!(
11569        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11570        0,
11571        "Buffer 3 was clean, so it should not have been written during autosave"
11572    );
11573
11574    // Verify buffer states after autosave
11575    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11576    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11577    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11578
11579    // Now perform a manual save (format = true)
11580    let save_task = editor.update_in(cx, |editor, window, cx| {
11581        editor.save(
11582            SaveOptions {
11583                format: true,
11584                autosave: false,
11585            },
11586            project.clone(),
11587            window,
11588            cx,
11589        )
11590    });
11591    save_task.await.unwrap();
11592
11593    // During manual save, clean buffers don't get written to disk
11594    // They just get did_save called for language server notifications
11595    assert_eq!(
11596        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11597        1,
11598        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11599    );
11600    assert_eq!(
11601        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11602        0,
11603        "Buffer 2 should not have been written at all"
11604    );
11605    assert_eq!(
11606        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11607        0,
11608        "Buffer 3 should not have been written at all"
11609    );
11610}
11611
11612async fn setup_range_format_test(
11613    cx: &mut TestAppContext,
11614) -> (
11615    Entity<Project>,
11616    Entity<Editor>,
11617    &mut gpui::VisualTestContext,
11618    lsp::FakeLanguageServer,
11619) {
11620    init_test(cx, |_| {});
11621
11622    let fs = FakeFs::new(cx.executor());
11623    fs.insert_file(path!("/file.rs"), Default::default()).await;
11624
11625    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11626
11627    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11628    language_registry.add(rust_lang());
11629    let mut fake_servers = language_registry.register_fake_lsp(
11630        "Rust",
11631        FakeLspAdapter {
11632            capabilities: lsp::ServerCapabilities {
11633                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11634                ..lsp::ServerCapabilities::default()
11635            },
11636            ..FakeLspAdapter::default()
11637        },
11638    );
11639
11640    let buffer = project
11641        .update(cx, |project, cx| {
11642            project.open_local_buffer(path!("/file.rs"), cx)
11643        })
11644        .await
11645        .unwrap();
11646
11647    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11648    let (editor, cx) = cx.add_window_view(|window, cx| {
11649        build_editor_with_project(project.clone(), buffer, window, cx)
11650    });
11651
11652    cx.executor().start_waiting();
11653    let fake_server = fake_servers.next().await.unwrap();
11654
11655    (project, editor, cx, fake_server)
11656}
11657
11658#[gpui::test]
11659async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11660    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11661
11662    editor.update_in(cx, |editor, window, cx| {
11663        editor.set_text("one\ntwo\nthree\n", window, cx)
11664    });
11665    assert!(cx.read(|cx| editor.is_dirty(cx)));
11666
11667    let save = editor
11668        .update_in(cx, |editor, window, cx| {
11669            editor.save(
11670                SaveOptions {
11671                    format: true,
11672                    autosave: false,
11673                },
11674                project.clone(),
11675                window,
11676                cx,
11677            )
11678        })
11679        .unwrap();
11680    fake_server
11681        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11682            assert_eq!(
11683                params.text_document.uri,
11684                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11685            );
11686            assert_eq!(params.options.tab_size, 4);
11687            Ok(Some(vec![lsp::TextEdit::new(
11688                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11689                ", ".to_string(),
11690            )]))
11691        })
11692        .next()
11693        .await;
11694    cx.executor().start_waiting();
11695    save.await;
11696    assert_eq!(
11697        editor.update(cx, |editor, cx| editor.text(cx)),
11698        "one, two\nthree\n"
11699    );
11700    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11701}
11702
11703#[gpui::test]
11704async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11705    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11706
11707    editor.update_in(cx, |editor, window, cx| {
11708        editor.set_text("one\ntwo\nthree\n", window, cx)
11709    });
11710    assert!(cx.read(|cx| editor.is_dirty(cx)));
11711
11712    // Test that save still works when formatting hangs
11713    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11714        move |params, _| async move {
11715            assert_eq!(
11716                params.text_document.uri,
11717                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11718            );
11719            futures::future::pending::<()>().await;
11720            unreachable!()
11721        },
11722    );
11723    let save = editor
11724        .update_in(cx, |editor, window, cx| {
11725            editor.save(
11726                SaveOptions {
11727                    format: true,
11728                    autosave: false,
11729                },
11730                project.clone(),
11731                window,
11732                cx,
11733            )
11734        })
11735        .unwrap();
11736    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11737    cx.executor().start_waiting();
11738    save.await;
11739    assert_eq!(
11740        editor.update(cx, |editor, cx| editor.text(cx)),
11741        "one\ntwo\nthree\n"
11742    );
11743    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11744}
11745
11746#[gpui::test]
11747async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11748    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11749
11750    // Buffer starts clean, no formatting should be requested
11751    let save = editor
11752        .update_in(cx, |editor, window, cx| {
11753            editor.save(
11754                SaveOptions {
11755                    format: false,
11756                    autosave: false,
11757                },
11758                project.clone(),
11759                window,
11760                cx,
11761            )
11762        })
11763        .unwrap();
11764    let _pending_format_request = fake_server
11765        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11766            panic!("Should not be invoked");
11767        })
11768        .next();
11769    cx.executor().start_waiting();
11770    save.await;
11771    cx.run_until_parked();
11772}
11773
11774#[gpui::test]
11775async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11776    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11777
11778    // Set Rust language override and assert overridden tabsize is sent to language server
11779    update_test_language_settings(cx, |settings| {
11780        settings.languages.0.insert(
11781            "Rust".into(),
11782            LanguageSettingsContent {
11783                tab_size: NonZeroU32::new(8),
11784                ..Default::default()
11785            },
11786        );
11787    });
11788
11789    editor.update_in(cx, |editor, window, cx| {
11790        editor.set_text("something_new\n", window, cx)
11791    });
11792    assert!(cx.read(|cx| editor.is_dirty(cx)));
11793    let save = editor
11794        .update_in(cx, |editor, window, cx| {
11795            editor.save(
11796                SaveOptions {
11797                    format: true,
11798                    autosave: false,
11799                },
11800                project.clone(),
11801                window,
11802                cx,
11803            )
11804        })
11805        .unwrap();
11806    fake_server
11807        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11808            assert_eq!(
11809                params.text_document.uri,
11810                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11811            );
11812            assert_eq!(params.options.tab_size, 8);
11813            Ok(Some(Vec::new()))
11814        })
11815        .next()
11816        .await;
11817    save.await;
11818}
11819
11820#[gpui::test]
11821async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
11822    init_test(cx, |settings| {
11823        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
11824            settings::LanguageServerFormatterSpecifier::Current,
11825        )))
11826    });
11827
11828    let fs = FakeFs::new(cx.executor());
11829    fs.insert_file(path!("/file.rs"), Default::default()).await;
11830
11831    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11832
11833    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11834    language_registry.add(Arc::new(Language::new(
11835        LanguageConfig {
11836            name: "Rust".into(),
11837            matcher: LanguageMatcher {
11838                path_suffixes: vec!["rs".to_string()],
11839                ..Default::default()
11840            },
11841            ..LanguageConfig::default()
11842        },
11843        Some(tree_sitter_rust::LANGUAGE.into()),
11844    )));
11845    update_test_language_settings(cx, |settings| {
11846        // Enable Prettier formatting for the same buffer, and ensure
11847        // LSP is called instead of Prettier.
11848        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
11849    });
11850    let mut fake_servers = language_registry.register_fake_lsp(
11851        "Rust",
11852        FakeLspAdapter {
11853            capabilities: lsp::ServerCapabilities {
11854                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11855                ..Default::default()
11856            },
11857            ..Default::default()
11858        },
11859    );
11860
11861    let buffer = project
11862        .update(cx, |project, cx| {
11863            project.open_local_buffer(path!("/file.rs"), cx)
11864        })
11865        .await
11866        .unwrap();
11867
11868    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11869    let (editor, cx) = cx.add_window_view(|window, cx| {
11870        build_editor_with_project(project.clone(), buffer, window, cx)
11871    });
11872    editor.update_in(cx, |editor, window, cx| {
11873        editor.set_text("one\ntwo\nthree\n", window, cx)
11874    });
11875
11876    cx.executor().start_waiting();
11877    let fake_server = fake_servers.next().await.unwrap();
11878
11879    let format = editor
11880        .update_in(cx, |editor, window, cx| {
11881            editor.perform_format(
11882                project.clone(),
11883                FormatTrigger::Manual,
11884                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11885                window,
11886                cx,
11887            )
11888        })
11889        .unwrap();
11890    fake_server
11891        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11892            assert_eq!(
11893                params.text_document.uri,
11894                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11895            );
11896            assert_eq!(params.options.tab_size, 4);
11897            Ok(Some(vec![lsp::TextEdit::new(
11898                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11899                ", ".to_string(),
11900            )]))
11901        })
11902        .next()
11903        .await;
11904    cx.executor().start_waiting();
11905    format.await;
11906    assert_eq!(
11907        editor.update(cx, |editor, cx| editor.text(cx)),
11908        "one, two\nthree\n"
11909    );
11910
11911    editor.update_in(cx, |editor, window, cx| {
11912        editor.set_text("one\ntwo\nthree\n", window, cx)
11913    });
11914    // Ensure we don't lock if formatting hangs.
11915    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11916        move |params, _| async move {
11917            assert_eq!(
11918                params.text_document.uri,
11919                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11920            );
11921            futures::future::pending::<()>().await;
11922            unreachable!()
11923        },
11924    );
11925    let format = editor
11926        .update_in(cx, |editor, window, cx| {
11927            editor.perform_format(
11928                project,
11929                FormatTrigger::Manual,
11930                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11931                window,
11932                cx,
11933            )
11934        })
11935        .unwrap();
11936    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11937    cx.executor().start_waiting();
11938    format.await;
11939    assert_eq!(
11940        editor.update(cx, |editor, cx| editor.text(cx)),
11941        "one\ntwo\nthree\n"
11942    );
11943}
11944
11945#[gpui::test]
11946async fn test_multiple_formatters(cx: &mut TestAppContext) {
11947    init_test(cx, |settings| {
11948        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
11949        settings.defaults.formatter = Some(FormatterList::Vec(vec![
11950            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
11951            Formatter::CodeAction("code-action-1".into()),
11952            Formatter::CodeAction("code-action-2".into()),
11953        ]))
11954    });
11955
11956    let fs = FakeFs::new(cx.executor());
11957    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
11958        .await;
11959
11960    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11961    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11962    language_registry.add(rust_lang());
11963
11964    let mut fake_servers = language_registry.register_fake_lsp(
11965        "Rust",
11966        FakeLspAdapter {
11967            capabilities: lsp::ServerCapabilities {
11968                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11969                execute_command_provider: Some(lsp::ExecuteCommandOptions {
11970                    commands: vec!["the-command-for-code-action-1".into()],
11971                    ..Default::default()
11972                }),
11973                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11974                ..Default::default()
11975            },
11976            ..Default::default()
11977        },
11978    );
11979
11980    let buffer = project
11981        .update(cx, |project, cx| {
11982            project.open_local_buffer(path!("/file.rs"), cx)
11983        })
11984        .await
11985        .unwrap();
11986
11987    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11988    let (editor, cx) = cx.add_window_view(|window, cx| {
11989        build_editor_with_project(project.clone(), buffer, window, cx)
11990    });
11991
11992    cx.executor().start_waiting();
11993
11994    let fake_server = fake_servers.next().await.unwrap();
11995    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11996        move |_params, _| async move {
11997            Ok(Some(vec![lsp::TextEdit::new(
11998                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11999                "applied-formatting\n".to_string(),
12000            )]))
12001        },
12002    );
12003    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12004        move |params, _| async move {
12005            let requested_code_actions = params.context.only.expect("Expected code action request");
12006            assert_eq!(requested_code_actions.len(), 1);
12007
12008            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12009            let code_action = match requested_code_actions[0].as_str() {
12010                "code-action-1" => lsp::CodeAction {
12011                    kind: Some("code-action-1".into()),
12012                    edit: Some(lsp::WorkspaceEdit::new(
12013                        [(
12014                            uri,
12015                            vec![lsp::TextEdit::new(
12016                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12017                                "applied-code-action-1-edit\n".to_string(),
12018                            )],
12019                        )]
12020                        .into_iter()
12021                        .collect(),
12022                    )),
12023                    command: Some(lsp::Command {
12024                        command: "the-command-for-code-action-1".into(),
12025                        ..Default::default()
12026                    }),
12027                    ..Default::default()
12028                },
12029                "code-action-2" => lsp::CodeAction {
12030                    kind: Some("code-action-2".into()),
12031                    edit: Some(lsp::WorkspaceEdit::new(
12032                        [(
12033                            uri,
12034                            vec![lsp::TextEdit::new(
12035                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12036                                "applied-code-action-2-edit\n".to_string(),
12037                            )],
12038                        )]
12039                        .into_iter()
12040                        .collect(),
12041                    )),
12042                    ..Default::default()
12043                },
12044                req => panic!("Unexpected code action request: {:?}", req),
12045            };
12046            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12047                code_action,
12048            )]))
12049        },
12050    );
12051
12052    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12053        move |params, _| async move { Ok(params) }
12054    });
12055
12056    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12057    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12058        let fake = fake_server.clone();
12059        let lock = command_lock.clone();
12060        move |params, _| {
12061            assert_eq!(params.command, "the-command-for-code-action-1");
12062            let fake = fake.clone();
12063            let lock = lock.clone();
12064            async move {
12065                lock.lock().await;
12066                fake.server
12067                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12068                        label: None,
12069                        edit: lsp::WorkspaceEdit {
12070                            changes: Some(
12071                                [(
12072                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12073                                    vec![lsp::TextEdit {
12074                                        range: lsp::Range::new(
12075                                            lsp::Position::new(0, 0),
12076                                            lsp::Position::new(0, 0),
12077                                        ),
12078                                        new_text: "applied-code-action-1-command\n".into(),
12079                                    }],
12080                                )]
12081                                .into_iter()
12082                                .collect(),
12083                            ),
12084                            ..Default::default()
12085                        },
12086                    })
12087                    .await
12088                    .into_response()
12089                    .unwrap();
12090                Ok(Some(json!(null)))
12091            }
12092        }
12093    });
12094
12095    cx.executor().start_waiting();
12096    editor
12097        .update_in(cx, |editor, window, cx| {
12098            editor.perform_format(
12099                project.clone(),
12100                FormatTrigger::Manual,
12101                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12102                window,
12103                cx,
12104            )
12105        })
12106        .unwrap()
12107        .await;
12108    editor.update(cx, |editor, cx| {
12109        assert_eq!(
12110            editor.text(cx),
12111            r#"
12112                applied-code-action-2-edit
12113                applied-code-action-1-command
12114                applied-code-action-1-edit
12115                applied-formatting
12116                one
12117                two
12118                three
12119            "#
12120            .unindent()
12121        );
12122    });
12123
12124    editor.update_in(cx, |editor, window, cx| {
12125        editor.undo(&Default::default(), window, cx);
12126        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12127    });
12128
12129    // Perform a manual edit while waiting for an LSP command
12130    // that's being run as part of a formatting code action.
12131    let lock_guard = command_lock.lock().await;
12132    let format = editor
12133        .update_in(cx, |editor, window, cx| {
12134            editor.perform_format(
12135                project.clone(),
12136                FormatTrigger::Manual,
12137                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12138                window,
12139                cx,
12140            )
12141        })
12142        .unwrap();
12143    cx.run_until_parked();
12144    editor.update(cx, |editor, cx| {
12145        assert_eq!(
12146            editor.text(cx),
12147            r#"
12148                applied-code-action-1-edit
12149                applied-formatting
12150                one
12151                two
12152                three
12153            "#
12154            .unindent()
12155        );
12156
12157        editor.buffer.update(cx, |buffer, cx| {
12158            let ix = buffer.len(cx);
12159            buffer.edit([(ix..ix, "edited\n")], None, cx);
12160        });
12161    });
12162
12163    // Allow the LSP command to proceed. Because the buffer was edited,
12164    // the second code action will not be run.
12165    drop(lock_guard);
12166    format.await;
12167    editor.update_in(cx, |editor, window, cx| {
12168        assert_eq!(
12169            editor.text(cx),
12170            r#"
12171                applied-code-action-1-command
12172                applied-code-action-1-edit
12173                applied-formatting
12174                one
12175                two
12176                three
12177                edited
12178            "#
12179            .unindent()
12180        );
12181
12182        // The manual edit is undone first, because it is the last thing the user did
12183        // (even though the command completed afterwards).
12184        editor.undo(&Default::default(), window, cx);
12185        assert_eq!(
12186            editor.text(cx),
12187            r#"
12188                applied-code-action-1-command
12189                applied-code-action-1-edit
12190                applied-formatting
12191                one
12192                two
12193                three
12194            "#
12195            .unindent()
12196        );
12197
12198        // All the formatting (including the command, which completed after the manual edit)
12199        // is undone together.
12200        editor.undo(&Default::default(), window, cx);
12201        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12202    });
12203}
12204
12205#[gpui::test]
12206async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12207    init_test(cx, |settings| {
12208        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
12209            settings::LanguageServerFormatterSpecifier::Current,
12210        )]))
12211    });
12212
12213    let fs = FakeFs::new(cx.executor());
12214    fs.insert_file(path!("/file.ts"), Default::default()).await;
12215
12216    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12217
12218    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12219    language_registry.add(Arc::new(Language::new(
12220        LanguageConfig {
12221            name: "TypeScript".into(),
12222            matcher: LanguageMatcher {
12223                path_suffixes: vec!["ts".to_string()],
12224                ..Default::default()
12225            },
12226            ..LanguageConfig::default()
12227        },
12228        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12229    )));
12230    update_test_language_settings(cx, |settings| {
12231        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12232    });
12233    let mut fake_servers = language_registry.register_fake_lsp(
12234        "TypeScript",
12235        FakeLspAdapter {
12236            capabilities: lsp::ServerCapabilities {
12237                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12238                ..Default::default()
12239            },
12240            ..Default::default()
12241        },
12242    );
12243
12244    let buffer = project
12245        .update(cx, |project, cx| {
12246            project.open_local_buffer(path!("/file.ts"), cx)
12247        })
12248        .await
12249        .unwrap();
12250
12251    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12252    let (editor, cx) = cx.add_window_view(|window, cx| {
12253        build_editor_with_project(project.clone(), buffer, window, cx)
12254    });
12255    editor.update_in(cx, |editor, window, cx| {
12256        editor.set_text(
12257            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12258            window,
12259            cx,
12260        )
12261    });
12262
12263    cx.executor().start_waiting();
12264    let fake_server = fake_servers.next().await.unwrap();
12265
12266    let format = editor
12267        .update_in(cx, |editor, window, cx| {
12268            editor.perform_code_action_kind(
12269                project.clone(),
12270                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12271                window,
12272                cx,
12273            )
12274        })
12275        .unwrap();
12276    fake_server
12277        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12278            assert_eq!(
12279                params.text_document.uri,
12280                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12281            );
12282            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12283                lsp::CodeAction {
12284                    title: "Organize Imports".to_string(),
12285                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12286                    edit: Some(lsp::WorkspaceEdit {
12287                        changes: Some(
12288                            [(
12289                                params.text_document.uri.clone(),
12290                                vec![lsp::TextEdit::new(
12291                                    lsp::Range::new(
12292                                        lsp::Position::new(1, 0),
12293                                        lsp::Position::new(2, 0),
12294                                    ),
12295                                    "".to_string(),
12296                                )],
12297                            )]
12298                            .into_iter()
12299                            .collect(),
12300                        ),
12301                        ..Default::default()
12302                    }),
12303                    ..Default::default()
12304                },
12305            )]))
12306        })
12307        .next()
12308        .await;
12309    cx.executor().start_waiting();
12310    format.await;
12311    assert_eq!(
12312        editor.update(cx, |editor, cx| editor.text(cx)),
12313        "import { a } from 'module';\n\nconst x = a;\n"
12314    );
12315
12316    editor.update_in(cx, |editor, window, cx| {
12317        editor.set_text(
12318            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12319            window,
12320            cx,
12321        )
12322    });
12323    // Ensure we don't lock if code action hangs.
12324    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12325        move |params, _| async move {
12326            assert_eq!(
12327                params.text_document.uri,
12328                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12329            );
12330            futures::future::pending::<()>().await;
12331            unreachable!()
12332        },
12333    );
12334    let format = editor
12335        .update_in(cx, |editor, window, cx| {
12336            editor.perform_code_action_kind(
12337                project,
12338                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12339                window,
12340                cx,
12341            )
12342        })
12343        .unwrap();
12344    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12345    cx.executor().start_waiting();
12346    format.await;
12347    assert_eq!(
12348        editor.update(cx, |editor, cx| editor.text(cx)),
12349        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12350    );
12351}
12352
12353#[gpui::test]
12354async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12355    init_test(cx, |_| {});
12356
12357    let mut cx = EditorLspTestContext::new_rust(
12358        lsp::ServerCapabilities {
12359            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12360            ..Default::default()
12361        },
12362        cx,
12363    )
12364    .await;
12365
12366    cx.set_state(indoc! {"
12367        one.twoˇ
12368    "});
12369
12370    // The format request takes a long time. When it completes, it inserts
12371    // a newline and an indent before the `.`
12372    cx.lsp
12373        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12374            let executor = cx.background_executor().clone();
12375            async move {
12376                executor.timer(Duration::from_millis(100)).await;
12377                Ok(Some(vec![lsp::TextEdit {
12378                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12379                    new_text: "\n    ".into(),
12380                }]))
12381            }
12382        });
12383
12384    // Submit a format request.
12385    let format_1 = cx
12386        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12387        .unwrap();
12388    cx.executor().run_until_parked();
12389
12390    // Submit a second format request.
12391    let format_2 = cx
12392        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12393        .unwrap();
12394    cx.executor().run_until_parked();
12395
12396    // Wait for both format requests to complete
12397    cx.executor().advance_clock(Duration::from_millis(200));
12398    cx.executor().start_waiting();
12399    format_1.await.unwrap();
12400    cx.executor().start_waiting();
12401    format_2.await.unwrap();
12402
12403    // The formatting edits only happens once.
12404    cx.assert_editor_state(indoc! {"
12405        one
12406            .twoˇ
12407    "});
12408}
12409
12410#[gpui::test]
12411async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12412    init_test(cx, |settings| {
12413        settings.defaults.formatter = Some(FormatterList::default())
12414    });
12415
12416    let mut cx = EditorLspTestContext::new_rust(
12417        lsp::ServerCapabilities {
12418            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12419            ..Default::default()
12420        },
12421        cx,
12422    )
12423    .await;
12424
12425    // Set up a buffer white some trailing whitespace and no trailing newline.
12426    cx.set_state(
12427        &[
12428            "one ",   //
12429            "twoˇ",   //
12430            "three ", //
12431            "four",   //
12432        ]
12433        .join("\n"),
12434    );
12435
12436    // Submit a format request.
12437    let format = cx
12438        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12439        .unwrap();
12440
12441    // Record which buffer changes have been sent to the language server
12442    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12443    cx.lsp
12444        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12445            let buffer_changes = buffer_changes.clone();
12446            move |params, _| {
12447                buffer_changes.lock().extend(
12448                    params
12449                        .content_changes
12450                        .into_iter()
12451                        .map(|e| (e.range.unwrap(), e.text)),
12452                );
12453            }
12454        });
12455
12456    // Handle formatting requests to the language server.
12457    cx.lsp
12458        .set_request_handler::<lsp::request::Formatting, _, _>({
12459            let buffer_changes = buffer_changes.clone();
12460            move |_, _| {
12461                // When formatting is requested, trailing whitespace has already been stripped,
12462                // and the trailing newline has already been added.
12463                assert_eq!(
12464                    &buffer_changes.lock()[1..],
12465                    &[
12466                        (
12467                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12468                            "".into()
12469                        ),
12470                        (
12471                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12472                            "".into()
12473                        ),
12474                        (
12475                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12476                            "\n".into()
12477                        ),
12478                    ]
12479                );
12480
12481                // Insert blank lines between each line of the buffer.
12482                async move {
12483                    Ok(Some(vec![
12484                        lsp::TextEdit {
12485                            range: lsp::Range::new(
12486                                lsp::Position::new(1, 0),
12487                                lsp::Position::new(1, 0),
12488                            ),
12489                            new_text: "\n".into(),
12490                        },
12491                        lsp::TextEdit {
12492                            range: lsp::Range::new(
12493                                lsp::Position::new(2, 0),
12494                                lsp::Position::new(2, 0),
12495                            ),
12496                            new_text: "\n".into(),
12497                        },
12498                    ]))
12499                }
12500            }
12501        });
12502
12503    // After formatting the buffer, the trailing whitespace is stripped,
12504    // a newline is appended, and the edits provided by the language server
12505    // have been applied.
12506    format.await.unwrap();
12507    cx.assert_editor_state(
12508        &[
12509            "one",   //
12510            "",      //
12511            "twoˇ",  //
12512            "",      //
12513            "three", //
12514            "four",  //
12515            "",      //
12516        ]
12517        .join("\n"),
12518    );
12519
12520    // Undoing the formatting undoes the trailing whitespace removal, the
12521    // trailing newline, and the LSP edits.
12522    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12523    cx.assert_editor_state(
12524        &[
12525            "one ",   //
12526            "twoˇ",   //
12527            "three ", //
12528            "four",   //
12529        ]
12530        .join("\n"),
12531    );
12532}
12533
12534#[gpui::test]
12535async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12536    cx: &mut TestAppContext,
12537) {
12538    init_test(cx, |_| {});
12539
12540    cx.update(|cx| {
12541        cx.update_global::<SettingsStore, _>(|settings, cx| {
12542            settings.update_user_settings(cx, |settings| {
12543                settings.editor.auto_signature_help = Some(true);
12544            });
12545        });
12546    });
12547
12548    let mut cx = EditorLspTestContext::new_rust(
12549        lsp::ServerCapabilities {
12550            signature_help_provider: Some(lsp::SignatureHelpOptions {
12551                ..Default::default()
12552            }),
12553            ..Default::default()
12554        },
12555        cx,
12556    )
12557    .await;
12558
12559    let language = Language::new(
12560        LanguageConfig {
12561            name: "Rust".into(),
12562            brackets: BracketPairConfig {
12563                pairs: vec![
12564                    BracketPair {
12565                        start: "{".to_string(),
12566                        end: "}".to_string(),
12567                        close: true,
12568                        surround: true,
12569                        newline: true,
12570                    },
12571                    BracketPair {
12572                        start: "(".to_string(),
12573                        end: ")".to_string(),
12574                        close: true,
12575                        surround: true,
12576                        newline: true,
12577                    },
12578                    BracketPair {
12579                        start: "/*".to_string(),
12580                        end: " */".to_string(),
12581                        close: true,
12582                        surround: true,
12583                        newline: true,
12584                    },
12585                    BracketPair {
12586                        start: "[".to_string(),
12587                        end: "]".to_string(),
12588                        close: false,
12589                        surround: false,
12590                        newline: true,
12591                    },
12592                    BracketPair {
12593                        start: "\"".to_string(),
12594                        end: "\"".to_string(),
12595                        close: true,
12596                        surround: true,
12597                        newline: false,
12598                    },
12599                    BracketPair {
12600                        start: "<".to_string(),
12601                        end: ">".to_string(),
12602                        close: false,
12603                        surround: true,
12604                        newline: true,
12605                    },
12606                ],
12607                ..Default::default()
12608            },
12609            autoclose_before: "})]".to_string(),
12610            ..Default::default()
12611        },
12612        Some(tree_sitter_rust::LANGUAGE.into()),
12613    );
12614    let language = Arc::new(language);
12615
12616    cx.language_registry().add(language.clone());
12617    cx.update_buffer(|buffer, cx| {
12618        buffer.set_language(Some(language), cx);
12619    });
12620
12621    cx.set_state(
12622        &r#"
12623            fn main() {
12624                sampleˇ
12625            }
12626        "#
12627        .unindent(),
12628    );
12629
12630    cx.update_editor(|editor, window, cx| {
12631        editor.handle_input("(", window, cx);
12632    });
12633    cx.assert_editor_state(
12634        &"
12635            fn main() {
12636                sample(ˇ)
12637            }
12638        "
12639        .unindent(),
12640    );
12641
12642    let mocked_response = lsp::SignatureHelp {
12643        signatures: vec![lsp::SignatureInformation {
12644            label: "fn sample(param1: u8, param2: u8)".to_string(),
12645            documentation: None,
12646            parameters: Some(vec![
12647                lsp::ParameterInformation {
12648                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12649                    documentation: None,
12650                },
12651                lsp::ParameterInformation {
12652                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12653                    documentation: None,
12654                },
12655            ]),
12656            active_parameter: None,
12657        }],
12658        active_signature: Some(0),
12659        active_parameter: Some(0),
12660    };
12661    handle_signature_help_request(&mut cx, mocked_response).await;
12662
12663    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12664        .await;
12665
12666    cx.editor(|editor, _, _| {
12667        let signature_help_state = editor.signature_help_state.popover().cloned();
12668        let signature = signature_help_state.unwrap();
12669        assert_eq!(
12670            signature.signatures[signature.current_signature].label,
12671            "fn sample(param1: u8, param2: u8)"
12672        );
12673    });
12674}
12675
12676#[gpui::test]
12677async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12678    init_test(cx, |_| {});
12679
12680    cx.update(|cx| {
12681        cx.update_global::<SettingsStore, _>(|settings, cx| {
12682            settings.update_user_settings(cx, |settings| {
12683                settings.editor.auto_signature_help = Some(false);
12684                settings.editor.show_signature_help_after_edits = Some(false);
12685            });
12686        });
12687    });
12688
12689    let mut cx = EditorLspTestContext::new_rust(
12690        lsp::ServerCapabilities {
12691            signature_help_provider: Some(lsp::SignatureHelpOptions {
12692                ..Default::default()
12693            }),
12694            ..Default::default()
12695        },
12696        cx,
12697    )
12698    .await;
12699
12700    let language = Language::new(
12701        LanguageConfig {
12702            name: "Rust".into(),
12703            brackets: BracketPairConfig {
12704                pairs: vec![
12705                    BracketPair {
12706                        start: "{".to_string(),
12707                        end: "}".to_string(),
12708                        close: true,
12709                        surround: true,
12710                        newline: true,
12711                    },
12712                    BracketPair {
12713                        start: "(".to_string(),
12714                        end: ")".to_string(),
12715                        close: true,
12716                        surround: true,
12717                        newline: true,
12718                    },
12719                    BracketPair {
12720                        start: "/*".to_string(),
12721                        end: " */".to_string(),
12722                        close: true,
12723                        surround: true,
12724                        newline: true,
12725                    },
12726                    BracketPair {
12727                        start: "[".to_string(),
12728                        end: "]".to_string(),
12729                        close: false,
12730                        surround: false,
12731                        newline: true,
12732                    },
12733                    BracketPair {
12734                        start: "\"".to_string(),
12735                        end: "\"".to_string(),
12736                        close: true,
12737                        surround: true,
12738                        newline: false,
12739                    },
12740                    BracketPair {
12741                        start: "<".to_string(),
12742                        end: ">".to_string(),
12743                        close: false,
12744                        surround: true,
12745                        newline: true,
12746                    },
12747                ],
12748                ..Default::default()
12749            },
12750            autoclose_before: "})]".to_string(),
12751            ..Default::default()
12752        },
12753        Some(tree_sitter_rust::LANGUAGE.into()),
12754    );
12755    let language = Arc::new(language);
12756
12757    cx.language_registry().add(language.clone());
12758    cx.update_buffer(|buffer, cx| {
12759        buffer.set_language(Some(language), cx);
12760    });
12761
12762    // Ensure that signature_help is not called when no signature help is enabled.
12763    cx.set_state(
12764        &r#"
12765            fn main() {
12766                sampleˇ
12767            }
12768        "#
12769        .unindent(),
12770    );
12771    cx.update_editor(|editor, window, cx| {
12772        editor.handle_input("(", window, cx);
12773    });
12774    cx.assert_editor_state(
12775        &"
12776            fn main() {
12777                sample(ˇ)
12778            }
12779        "
12780        .unindent(),
12781    );
12782    cx.editor(|editor, _, _| {
12783        assert!(editor.signature_help_state.task().is_none());
12784    });
12785
12786    let mocked_response = lsp::SignatureHelp {
12787        signatures: vec![lsp::SignatureInformation {
12788            label: "fn sample(param1: u8, param2: u8)".to_string(),
12789            documentation: None,
12790            parameters: Some(vec![
12791                lsp::ParameterInformation {
12792                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12793                    documentation: None,
12794                },
12795                lsp::ParameterInformation {
12796                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12797                    documentation: None,
12798                },
12799            ]),
12800            active_parameter: None,
12801        }],
12802        active_signature: Some(0),
12803        active_parameter: Some(0),
12804    };
12805
12806    // Ensure that signature_help is called when enabled afte edits
12807    cx.update(|_, cx| {
12808        cx.update_global::<SettingsStore, _>(|settings, cx| {
12809            settings.update_user_settings(cx, |settings| {
12810                settings.editor.auto_signature_help = Some(false);
12811                settings.editor.show_signature_help_after_edits = Some(true);
12812            });
12813        });
12814    });
12815    cx.set_state(
12816        &r#"
12817            fn main() {
12818                sampleˇ
12819            }
12820        "#
12821        .unindent(),
12822    );
12823    cx.update_editor(|editor, window, cx| {
12824        editor.handle_input("(", window, cx);
12825    });
12826    cx.assert_editor_state(
12827        &"
12828            fn main() {
12829                sample(ˇ)
12830            }
12831        "
12832        .unindent(),
12833    );
12834    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12835    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12836        .await;
12837    cx.update_editor(|editor, _, _| {
12838        let signature_help_state = editor.signature_help_state.popover().cloned();
12839        assert!(signature_help_state.is_some());
12840        let signature = signature_help_state.unwrap();
12841        assert_eq!(
12842            signature.signatures[signature.current_signature].label,
12843            "fn sample(param1: u8, param2: u8)"
12844        );
12845        editor.signature_help_state = SignatureHelpState::default();
12846    });
12847
12848    // Ensure that signature_help is called when auto signature help override is enabled
12849    cx.update(|_, cx| {
12850        cx.update_global::<SettingsStore, _>(|settings, cx| {
12851            settings.update_user_settings(cx, |settings| {
12852                settings.editor.auto_signature_help = Some(true);
12853                settings.editor.show_signature_help_after_edits = Some(false);
12854            });
12855        });
12856    });
12857    cx.set_state(
12858        &r#"
12859            fn main() {
12860                sampleˇ
12861            }
12862        "#
12863        .unindent(),
12864    );
12865    cx.update_editor(|editor, window, cx| {
12866        editor.handle_input("(", window, cx);
12867    });
12868    cx.assert_editor_state(
12869        &"
12870            fn main() {
12871                sample(ˇ)
12872            }
12873        "
12874        .unindent(),
12875    );
12876    handle_signature_help_request(&mut cx, mocked_response).await;
12877    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12878        .await;
12879    cx.editor(|editor, _, _| {
12880        let signature_help_state = editor.signature_help_state.popover().cloned();
12881        assert!(signature_help_state.is_some());
12882        let signature = signature_help_state.unwrap();
12883        assert_eq!(
12884            signature.signatures[signature.current_signature].label,
12885            "fn sample(param1: u8, param2: u8)"
12886        );
12887    });
12888}
12889
12890#[gpui::test]
12891async fn test_signature_help(cx: &mut TestAppContext) {
12892    init_test(cx, |_| {});
12893    cx.update(|cx| {
12894        cx.update_global::<SettingsStore, _>(|settings, cx| {
12895            settings.update_user_settings(cx, |settings| {
12896                settings.editor.auto_signature_help = Some(true);
12897            });
12898        });
12899    });
12900
12901    let mut cx = EditorLspTestContext::new_rust(
12902        lsp::ServerCapabilities {
12903            signature_help_provider: Some(lsp::SignatureHelpOptions {
12904                ..Default::default()
12905            }),
12906            ..Default::default()
12907        },
12908        cx,
12909    )
12910    .await;
12911
12912    // A test that directly calls `show_signature_help`
12913    cx.update_editor(|editor, window, cx| {
12914        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12915    });
12916
12917    let mocked_response = lsp::SignatureHelp {
12918        signatures: vec![lsp::SignatureInformation {
12919            label: "fn sample(param1: u8, param2: u8)".to_string(),
12920            documentation: None,
12921            parameters: Some(vec![
12922                lsp::ParameterInformation {
12923                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12924                    documentation: None,
12925                },
12926                lsp::ParameterInformation {
12927                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12928                    documentation: None,
12929                },
12930            ]),
12931            active_parameter: None,
12932        }],
12933        active_signature: Some(0),
12934        active_parameter: Some(0),
12935    };
12936    handle_signature_help_request(&mut cx, mocked_response).await;
12937
12938    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12939        .await;
12940
12941    cx.editor(|editor, _, _| {
12942        let signature_help_state = editor.signature_help_state.popover().cloned();
12943        assert!(signature_help_state.is_some());
12944        let signature = signature_help_state.unwrap();
12945        assert_eq!(
12946            signature.signatures[signature.current_signature].label,
12947            "fn sample(param1: u8, param2: u8)"
12948        );
12949    });
12950
12951    // When exiting outside from inside the brackets, `signature_help` is closed.
12952    cx.set_state(indoc! {"
12953        fn main() {
12954            sample(ˇ);
12955        }
12956
12957        fn sample(param1: u8, param2: u8) {}
12958    "});
12959
12960    cx.update_editor(|editor, window, cx| {
12961        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12962            s.select_ranges([0..0])
12963        });
12964    });
12965
12966    let mocked_response = lsp::SignatureHelp {
12967        signatures: Vec::new(),
12968        active_signature: None,
12969        active_parameter: None,
12970    };
12971    handle_signature_help_request(&mut cx, mocked_response).await;
12972
12973    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12974        .await;
12975
12976    cx.editor(|editor, _, _| {
12977        assert!(!editor.signature_help_state.is_shown());
12978    });
12979
12980    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
12981    cx.set_state(indoc! {"
12982        fn main() {
12983            sample(ˇ);
12984        }
12985
12986        fn sample(param1: u8, param2: u8) {}
12987    "});
12988
12989    let mocked_response = lsp::SignatureHelp {
12990        signatures: vec![lsp::SignatureInformation {
12991            label: "fn sample(param1: u8, param2: u8)".to_string(),
12992            documentation: None,
12993            parameters: Some(vec![
12994                lsp::ParameterInformation {
12995                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12996                    documentation: None,
12997                },
12998                lsp::ParameterInformation {
12999                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13000                    documentation: None,
13001                },
13002            ]),
13003            active_parameter: None,
13004        }],
13005        active_signature: Some(0),
13006        active_parameter: Some(0),
13007    };
13008    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13009    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13010        .await;
13011    cx.editor(|editor, _, _| {
13012        assert!(editor.signature_help_state.is_shown());
13013    });
13014
13015    // Restore the popover with more parameter input
13016    cx.set_state(indoc! {"
13017        fn main() {
13018            sample(param1, param2ˇ);
13019        }
13020
13021        fn sample(param1: u8, param2: u8) {}
13022    "});
13023
13024    let mocked_response = lsp::SignatureHelp {
13025        signatures: vec![lsp::SignatureInformation {
13026            label: "fn sample(param1: u8, param2: u8)".to_string(),
13027            documentation: None,
13028            parameters: Some(vec![
13029                lsp::ParameterInformation {
13030                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13031                    documentation: None,
13032                },
13033                lsp::ParameterInformation {
13034                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13035                    documentation: None,
13036                },
13037            ]),
13038            active_parameter: None,
13039        }],
13040        active_signature: Some(0),
13041        active_parameter: Some(1),
13042    };
13043    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13044    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13045        .await;
13046
13047    // When selecting a range, the popover is gone.
13048    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13049    cx.update_editor(|editor, window, cx| {
13050        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13051            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13052        })
13053    });
13054    cx.assert_editor_state(indoc! {"
13055        fn main() {
13056            sample(param1, «ˇparam2»);
13057        }
13058
13059        fn sample(param1: u8, param2: u8) {}
13060    "});
13061    cx.editor(|editor, _, _| {
13062        assert!(!editor.signature_help_state.is_shown());
13063    });
13064
13065    // When unselecting again, the popover is back if within the brackets.
13066    cx.update_editor(|editor, window, cx| {
13067        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13068            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13069        })
13070    });
13071    cx.assert_editor_state(indoc! {"
13072        fn main() {
13073            sample(param1, ˇparam2);
13074        }
13075
13076        fn sample(param1: u8, param2: u8) {}
13077    "});
13078    handle_signature_help_request(&mut cx, mocked_response).await;
13079    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13080        .await;
13081    cx.editor(|editor, _, _| {
13082        assert!(editor.signature_help_state.is_shown());
13083    });
13084
13085    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13086    cx.update_editor(|editor, window, cx| {
13087        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13088            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13089            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13090        })
13091    });
13092    cx.assert_editor_state(indoc! {"
13093        fn main() {
13094            sample(param1, ˇparam2);
13095        }
13096
13097        fn sample(param1: u8, param2: u8) {}
13098    "});
13099
13100    let mocked_response = lsp::SignatureHelp {
13101        signatures: vec![lsp::SignatureInformation {
13102            label: "fn sample(param1: u8, param2: u8)".to_string(),
13103            documentation: None,
13104            parameters: Some(vec![
13105                lsp::ParameterInformation {
13106                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13107                    documentation: None,
13108                },
13109                lsp::ParameterInformation {
13110                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13111                    documentation: None,
13112                },
13113            ]),
13114            active_parameter: None,
13115        }],
13116        active_signature: Some(0),
13117        active_parameter: Some(1),
13118    };
13119    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13120    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13121        .await;
13122    cx.update_editor(|editor, _, cx| {
13123        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13124    });
13125    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13126        .await;
13127    cx.update_editor(|editor, window, cx| {
13128        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13129            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13130        })
13131    });
13132    cx.assert_editor_state(indoc! {"
13133        fn main() {
13134            sample(param1, «ˇparam2»);
13135        }
13136
13137        fn sample(param1: u8, param2: u8) {}
13138    "});
13139    cx.update_editor(|editor, window, cx| {
13140        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13141            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13142        })
13143    });
13144    cx.assert_editor_state(indoc! {"
13145        fn main() {
13146            sample(param1, ˇparam2);
13147        }
13148
13149        fn sample(param1: u8, param2: u8) {}
13150    "});
13151    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13152        .await;
13153}
13154
13155#[gpui::test]
13156async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13157    init_test(cx, |_| {});
13158
13159    let mut cx = EditorLspTestContext::new_rust(
13160        lsp::ServerCapabilities {
13161            signature_help_provider: Some(lsp::SignatureHelpOptions {
13162                ..Default::default()
13163            }),
13164            ..Default::default()
13165        },
13166        cx,
13167    )
13168    .await;
13169
13170    cx.set_state(indoc! {"
13171        fn main() {
13172            overloadedˇ
13173        }
13174    "});
13175
13176    cx.update_editor(|editor, window, cx| {
13177        editor.handle_input("(", window, cx);
13178        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13179    });
13180
13181    // Mock response with 3 signatures
13182    let mocked_response = lsp::SignatureHelp {
13183        signatures: vec![
13184            lsp::SignatureInformation {
13185                label: "fn overloaded(x: i32)".to_string(),
13186                documentation: None,
13187                parameters: Some(vec![lsp::ParameterInformation {
13188                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13189                    documentation: None,
13190                }]),
13191                active_parameter: None,
13192            },
13193            lsp::SignatureInformation {
13194                label: "fn overloaded(x: i32, y: i32)".to_string(),
13195                documentation: None,
13196                parameters: Some(vec![
13197                    lsp::ParameterInformation {
13198                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13199                        documentation: None,
13200                    },
13201                    lsp::ParameterInformation {
13202                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13203                        documentation: None,
13204                    },
13205                ]),
13206                active_parameter: None,
13207            },
13208            lsp::SignatureInformation {
13209                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13210                documentation: None,
13211                parameters: Some(vec![
13212                    lsp::ParameterInformation {
13213                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13214                        documentation: None,
13215                    },
13216                    lsp::ParameterInformation {
13217                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13218                        documentation: None,
13219                    },
13220                    lsp::ParameterInformation {
13221                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13222                        documentation: None,
13223                    },
13224                ]),
13225                active_parameter: None,
13226            },
13227        ],
13228        active_signature: Some(1),
13229        active_parameter: Some(0),
13230    };
13231    handle_signature_help_request(&mut cx, mocked_response).await;
13232
13233    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13234        .await;
13235
13236    // Verify we have multiple signatures and the right one is selected
13237    cx.editor(|editor, _, _| {
13238        let popover = editor.signature_help_state.popover().cloned().unwrap();
13239        assert_eq!(popover.signatures.len(), 3);
13240        // active_signature was 1, so that should be the current
13241        assert_eq!(popover.current_signature, 1);
13242        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13243        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13244        assert_eq!(
13245            popover.signatures[2].label,
13246            "fn overloaded(x: i32, y: i32, z: i32)"
13247        );
13248    });
13249
13250    // Test navigation functionality
13251    cx.update_editor(|editor, window, cx| {
13252        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13253    });
13254
13255    cx.editor(|editor, _, _| {
13256        let popover = editor.signature_help_state.popover().cloned().unwrap();
13257        assert_eq!(popover.current_signature, 2);
13258    });
13259
13260    // Test wrap around
13261    cx.update_editor(|editor, window, cx| {
13262        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13263    });
13264
13265    cx.editor(|editor, _, _| {
13266        let popover = editor.signature_help_state.popover().cloned().unwrap();
13267        assert_eq!(popover.current_signature, 0);
13268    });
13269
13270    // Test previous navigation
13271    cx.update_editor(|editor, window, cx| {
13272        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13273    });
13274
13275    cx.editor(|editor, _, _| {
13276        let popover = editor.signature_help_state.popover().cloned().unwrap();
13277        assert_eq!(popover.current_signature, 2);
13278    });
13279}
13280
13281#[gpui::test]
13282async fn test_completion_mode(cx: &mut TestAppContext) {
13283    init_test(cx, |_| {});
13284    let mut cx = EditorLspTestContext::new_rust(
13285        lsp::ServerCapabilities {
13286            completion_provider: Some(lsp::CompletionOptions {
13287                resolve_provider: Some(true),
13288                ..Default::default()
13289            }),
13290            ..Default::default()
13291        },
13292        cx,
13293    )
13294    .await;
13295
13296    struct Run {
13297        run_description: &'static str,
13298        initial_state: String,
13299        buffer_marked_text: String,
13300        completion_label: &'static str,
13301        completion_text: &'static str,
13302        expected_with_insert_mode: String,
13303        expected_with_replace_mode: String,
13304        expected_with_replace_subsequence_mode: String,
13305        expected_with_replace_suffix_mode: String,
13306    }
13307
13308    let runs = [
13309        Run {
13310            run_description: "Start of word matches completion text",
13311            initial_state: "before ediˇ after".into(),
13312            buffer_marked_text: "before <edi|> after".into(),
13313            completion_label: "editor",
13314            completion_text: "editor",
13315            expected_with_insert_mode: "before editorˇ after".into(),
13316            expected_with_replace_mode: "before editorˇ after".into(),
13317            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13318            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13319        },
13320        Run {
13321            run_description: "Accept same text at the middle of the word",
13322            initial_state: "before ediˇtor after".into(),
13323            buffer_marked_text: "before <edi|tor> after".into(),
13324            completion_label: "editor",
13325            completion_text: "editor",
13326            expected_with_insert_mode: "before editorˇtor after".into(),
13327            expected_with_replace_mode: "before editorˇ after".into(),
13328            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13329            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13330        },
13331        Run {
13332            run_description: "End of word matches completion text -- cursor at end",
13333            initial_state: "before torˇ after".into(),
13334            buffer_marked_text: "before <tor|> after".into(),
13335            completion_label: "editor",
13336            completion_text: "editor",
13337            expected_with_insert_mode: "before editorˇ after".into(),
13338            expected_with_replace_mode: "before editorˇ after".into(),
13339            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13340            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13341        },
13342        Run {
13343            run_description: "End of word matches completion text -- cursor at start",
13344            initial_state: "before ˇtor after".into(),
13345            buffer_marked_text: "before <|tor> after".into(),
13346            completion_label: "editor",
13347            completion_text: "editor",
13348            expected_with_insert_mode: "before editorˇtor after".into(),
13349            expected_with_replace_mode: "before editorˇ after".into(),
13350            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13351            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13352        },
13353        Run {
13354            run_description: "Prepend text containing whitespace",
13355            initial_state: "pˇfield: bool".into(),
13356            buffer_marked_text: "<p|field>: bool".into(),
13357            completion_label: "pub ",
13358            completion_text: "pub ",
13359            expected_with_insert_mode: "pub ˇfield: bool".into(),
13360            expected_with_replace_mode: "pub ˇ: bool".into(),
13361            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13362            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13363        },
13364        Run {
13365            run_description: "Add element to start of list",
13366            initial_state: "[element_ˇelement_2]".into(),
13367            buffer_marked_text: "[<element_|element_2>]".into(),
13368            completion_label: "element_1",
13369            completion_text: "element_1",
13370            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13371            expected_with_replace_mode: "[element_1ˇ]".into(),
13372            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13373            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13374        },
13375        Run {
13376            run_description: "Add element to start of list -- first and second elements are equal",
13377            initial_state: "[elˇelement]".into(),
13378            buffer_marked_text: "[<el|element>]".into(),
13379            completion_label: "element",
13380            completion_text: "element",
13381            expected_with_insert_mode: "[elementˇelement]".into(),
13382            expected_with_replace_mode: "[elementˇ]".into(),
13383            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13384            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13385        },
13386        Run {
13387            run_description: "Ends with matching suffix",
13388            initial_state: "SubˇError".into(),
13389            buffer_marked_text: "<Sub|Error>".into(),
13390            completion_label: "SubscriptionError",
13391            completion_text: "SubscriptionError",
13392            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13393            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13394            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13395            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13396        },
13397        Run {
13398            run_description: "Suffix is a subsequence -- contiguous",
13399            initial_state: "SubˇErr".into(),
13400            buffer_marked_text: "<Sub|Err>".into(),
13401            completion_label: "SubscriptionError",
13402            completion_text: "SubscriptionError",
13403            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13404            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13405            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13406            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13407        },
13408        Run {
13409            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13410            initial_state: "Suˇscrirr".into(),
13411            buffer_marked_text: "<Su|scrirr>".into(),
13412            completion_label: "SubscriptionError",
13413            completion_text: "SubscriptionError",
13414            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13415            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13416            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13417            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13418        },
13419        Run {
13420            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13421            initial_state: "foo(indˇix)".into(),
13422            buffer_marked_text: "foo(<ind|ix>)".into(),
13423            completion_label: "node_index",
13424            completion_text: "node_index",
13425            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13426            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13427            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13428            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13429        },
13430        Run {
13431            run_description: "Replace range ends before cursor - should extend to cursor",
13432            initial_state: "before editˇo after".into(),
13433            buffer_marked_text: "before <{ed}>it|o after".into(),
13434            completion_label: "editor",
13435            completion_text: "editor",
13436            expected_with_insert_mode: "before editorˇo after".into(),
13437            expected_with_replace_mode: "before editorˇo after".into(),
13438            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13439            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13440        },
13441        Run {
13442            run_description: "Uses label for suffix matching",
13443            initial_state: "before ediˇtor after".into(),
13444            buffer_marked_text: "before <edi|tor> after".into(),
13445            completion_label: "editor",
13446            completion_text: "editor()",
13447            expected_with_insert_mode: "before editor()ˇtor after".into(),
13448            expected_with_replace_mode: "before editor()ˇ after".into(),
13449            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13450            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13451        },
13452        Run {
13453            run_description: "Case insensitive subsequence and suffix matching",
13454            initial_state: "before EDiˇtoR after".into(),
13455            buffer_marked_text: "before <EDi|toR> after".into(),
13456            completion_label: "editor",
13457            completion_text: "editor",
13458            expected_with_insert_mode: "before editorˇtoR after".into(),
13459            expected_with_replace_mode: "before editorˇ after".into(),
13460            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13461            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13462        },
13463    ];
13464
13465    for run in runs {
13466        let run_variations = [
13467            (LspInsertMode::Insert, run.expected_with_insert_mode),
13468            (LspInsertMode::Replace, run.expected_with_replace_mode),
13469            (
13470                LspInsertMode::ReplaceSubsequence,
13471                run.expected_with_replace_subsequence_mode,
13472            ),
13473            (
13474                LspInsertMode::ReplaceSuffix,
13475                run.expected_with_replace_suffix_mode,
13476            ),
13477        ];
13478
13479        for (lsp_insert_mode, expected_text) in run_variations {
13480            eprintln!(
13481                "run = {:?}, mode = {lsp_insert_mode:.?}",
13482                run.run_description,
13483            );
13484
13485            update_test_language_settings(&mut cx, |settings| {
13486                settings.defaults.completions = Some(CompletionSettingsContent {
13487                    lsp_insert_mode: Some(lsp_insert_mode),
13488                    words: Some(WordsCompletionMode::Disabled),
13489                    words_min_length: Some(0),
13490                    ..Default::default()
13491                });
13492            });
13493
13494            cx.set_state(&run.initial_state);
13495            cx.update_editor(|editor, window, cx| {
13496                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13497            });
13498
13499            let counter = Arc::new(AtomicUsize::new(0));
13500            handle_completion_request_with_insert_and_replace(
13501                &mut cx,
13502                &run.buffer_marked_text,
13503                vec![(run.completion_label, run.completion_text)],
13504                counter.clone(),
13505            )
13506            .await;
13507            cx.condition(|editor, _| editor.context_menu_visible())
13508                .await;
13509            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13510
13511            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13512                editor
13513                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13514                    .unwrap()
13515            });
13516            cx.assert_editor_state(&expected_text);
13517            handle_resolve_completion_request(&mut cx, None).await;
13518            apply_additional_edits.await.unwrap();
13519        }
13520    }
13521}
13522
13523#[gpui::test]
13524async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13525    init_test(cx, |_| {});
13526    let mut cx = EditorLspTestContext::new_rust(
13527        lsp::ServerCapabilities {
13528            completion_provider: Some(lsp::CompletionOptions {
13529                resolve_provider: Some(true),
13530                ..Default::default()
13531            }),
13532            ..Default::default()
13533        },
13534        cx,
13535    )
13536    .await;
13537
13538    let initial_state = "SubˇError";
13539    let buffer_marked_text = "<Sub|Error>";
13540    let completion_text = "SubscriptionError";
13541    let expected_with_insert_mode = "SubscriptionErrorˇError";
13542    let expected_with_replace_mode = "SubscriptionErrorˇ";
13543
13544    update_test_language_settings(&mut cx, |settings| {
13545        settings.defaults.completions = Some(CompletionSettingsContent {
13546            words: Some(WordsCompletionMode::Disabled),
13547            words_min_length: Some(0),
13548            // set the opposite here to ensure that the action is overriding the default behavior
13549            lsp_insert_mode: Some(LspInsertMode::Insert),
13550            ..Default::default()
13551        });
13552    });
13553
13554    cx.set_state(initial_state);
13555    cx.update_editor(|editor, window, cx| {
13556        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13557    });
13558
13559    let counter = Arc::new(AtomicUsize::new(0));
13560    handle_completion_request_with_insert_and_replace(
13561        &mut cx,
13562        buffer_marked_text,
13563        vec![(completion_text, completion_text)],
13564        counter.clone(),
13565    )
13566    .await;
13567    cx.condition(|editor, _| editor.context_menu_visible())
13568        .await;
13569    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13570
13571    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13572        editor
13573            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13574            .unwrap()
13575    });
13576    cx.assert_editor_state(expected_with_replace_mode);
13577    handle_resolve_completion_request(&mut cx, None).await;
13578    apply_additional_edits.await.unwrap();
13579
13580    update_test_language_settings(&mut cx, |settings| {
13581        settings.defaults.completions = Some(CompletionSettingsContent {
13582            words: Some(WordsCompletionMode::Disabled),
13583            words_min_length: Some(0),
13584            // set the opposite here to ensure that the action is overriding the default behavior
13585            lsp_insert_mode: Some(LspInsertMode::Replace),
13586            ..Default::default()
13587        });
13588    });
13589
13590    cx.set_state(initial_state);
13591    cx.update_editor(|editor, window, cx| {
13592        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13593    });
13594    handle_completion_request_with_insert_and_replace(
13595        &mut cx,
13596        buffer_marked_text,
13597        vec![(completion_text, completion_text)],
13598        counter.clone(),
13599    )
13600    .await;
13601    cx.condition(|editor, _| editor.context_menu_visible())
13602        .await;
13603    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13604
13605    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13606        editor
13607            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13608            .unwrap()
13609    });
13610    cx.assert_editor_state(expected_with_insert_mode);
13611    handle_resolve_completion_request(&mut cx, None).await;
13612    apply_additional_edits.await.unwrap();
13613}
13614
13615#[gpui::test]
13616async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13617    init_test(cx, |_| {});
13618    let mut cx = EditorLspTestContext::new_rust(
13619        lsp::ServerCapabilities {
13620            completion_provider: Some(lsp::CompletionOptions {
13621                resolve_provider: Some(true),
13622                ..Default::default()
13623            }),
13624            ..Default::default()
13625        },
13626        cx,
13627    )
13628    .await;
13629
13630    // scenario: surrounding text matches completion text
13631    let completion_text = "to_offset";
13632    let initial_state = indoc! {"
13633        1. buf.to_offˇsuffix
13634        2. buf.to_offˇsuf
13635        3. buf.to_offˇfix
13636        4. buf.to_offˇ
13637        5. into_offˇensive
13638        6. ˇsuffix
13639        7. let ˇ //
13640        8. aaˇzz
13641        9. buf.to_off«zzzzzˇ»suffix
13642        10. buf.«ˇzzzzz»suffix
13643        11. to_off«ˇzzzzz»
13644
13645        buf.to_offˇsuffix  // newest cursor
13646    "};
13647    let completion_marked_buffer = indoc! {"
13648        1. buf.to_offsuffix
13649        2. buf.to_offsuf
13650        3. buf.to_offfix
13651        4. buf.to_off
13652        5. into_offensive
13653        6. suffix
13654        7. let  //
13655        8. aazz
13656        9. buf.to_offzzzzzsuffix
13657        10. buf.zzzzzsuffix
13658        11. to_offzzzzz
13659
13660        buf.<to_off|suffix>  // newest cursor
13661    "};
13662    let expected = indoc! {"
13663        1. buf.to_offsetˇ
13664        2. buf.to_offsetˇsuf
13665        3. buf.to_offsetˇfix
13666        4. buf.to_offsetˇ
13667        5. into_offsetˇensive
13668        6. to_offsetˇsuffix
13669        7. let to_offsetˇ //
13670        8. aato_offsetˇzz
13671        9. buf.to_offsetˇ
13672        10. buf.to_offsetˇsuffix
13673        11. to_offsetˇ
13674
13675        buf.to_offsetˇ  // newest cursor
13676    "};
13677    cx.set_state(initial_state);
13678    cx.update_editor(|editor, window, cx| {
13679        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13680    });
13681    handle_completion_request_with_insert_and_replace(
13682        &mut cx,
13683        completion_marked_buffer,
13684        vec![(completion_text, completion_text)],
13685        Arc::new(AtomicUsize::new(0)),
13686    )
13687    .await;
13688    cx.condition(|editor, _| editor.context_menu_visible())
13689        .await;
13690    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13691        editor
13692            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13693            .unwrap()
13694    });
13695    cx.assert_editor_state(expected);
13696    handle_resolve_completion_request(&mut cx, None).await;
13697    apply_additional_edits.await.unwrap();
13698
13699    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13700    let completion_text = "foo_and_bar";
13701    let initial_state = indoc! {"
13702        1. ooanbˇ
13703        2. zooanbˇ
13704        3. ooanbˇz
13705        4. zooanbˇz
13706        5. ooanˇ
13707        6. oanbˇ
13708
13709        ooanbˇ
13710    "};
13711    let completion_marked_buffer = indoc! {"
13712        1. ooanb
13713        2. zooanb
13714        3. ooanbz
13715        4. zooanbz
13716        5. ooan
13717        6. oanb
13718
13719        <ooanb|>
13720    "};
13721    let expected = indoc! {"
13722        1. foo_and_barˇ
13723        2. zfoo_and_barˇ
13724        3. foo_and_barˇz
13725        4. zfoo_and_barˇz
13726        5. ooanfoo_and_barˇ
13727        6. oanbfoo_and_barˇ
13728
13729        foo_and_barˇ
13730    "};
13731    cx.set_state(initial_state);
13732    cx.update_editor(|editor, window, cx| {
13733        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13734    });
13735    handle_completion_request_with_insert_and_replace(
13736        &mut cx,
13737        completion_marked_buffer,
13738        vec![(completion_text, completion_text)],
13739        Arc::new(AtomicUsize::new(0)),
13740    )
13741    .await;
13742    cx.condition(|editor, _| editor.context_menu_visible())
13743        .await;
13744    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13745        editor
13746            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13747            .unwrap()
13748    });
13749    cx.assert_editor_state(expected);
13750    handle_resolve_completion_request(&mut cx, None).await;
13751    apply_additional_edits.await.unwrap();
13752
13753    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13754    // (expects the same as if it was inserted at the end)
13755    let completion_text = "foo_and_bar";
13756    let initial_state = indoc! {"
13757        1. ooˇanb
13758        2. zooˇanb
13759        3. ooˇanbz
13760        4. zooˇanbz
13761
13762        ooˇanb
13763    "};
13764    let completion_marked_buffer = indoc! {"
13765        1. ooanb
13766        2. zooanb
13767        3. ooanbz
13768        4. zooanbz
13769
13770        <oo|anb>
13771    "};
13772    let expected = indoc! {"
13773        1. foo_and_barˇ
13774        2. zfoo_and_barˇ
13775        3. foo_and_barˇz
13776        4. zfoo_and_barˇz
13777
13778        foo_and_barˇ
13779    "};
13780    cx.set_state(initial_state);
13781    cx.update_editor(|editor, window, cx| {
13782        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13783    });
13784    handle_completion_request_with_insert_and_replace(
13785        &mut cx,
13786        completion_marked_buffer,
13787        vec![(completion_text, completion_text)],
13788        Arc::new(AtomicUsize::new(0)),
13789    )
13790    .await;
13791    cx.condition(|editor, _| editor.context_menu_visible())
13792        .await;
13793    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13794        editor
13795            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13796            .unwrap()
13797    });
13798    cx.assert_editor_state(expected);
13799    handle_resolve_completion_request(&mut cx, None).await;
13800    apply_additional_edits.await.unwrap();
13801}
13802
13803// This used to crash
13804#[gpui::test]
13805async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13806    init_test(cx, |_| {});
13807
13808    let buffer_text = indoc! {"
13809        fn main() {
13810            10.satu;
13811
13812            //
13813            // separate cursors so they open in different excerpts (manually reproducible)
13814            //
13815
13816            10.satu20;
13817        }
13818    "};
13819    let multibuffer_text_with_selections = indoc! {"
13820        fn main() {
13821            10.satuˇ;
13822
13823            //
13824
13825            //
13826
13827            10.satuˇ20;
13828        }
13829    "};
13830    let expected_multibuffer = indoc! {"
13831        fn main() {
13832            10.saturating_sub()ˇ;
13833
13834            //
13835
13836            //
13837
13838            10.saturating_sub()ˇ;
13839        }
13840    "};
13841
13842    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13843    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13844
13845    let fs = FakeFs::new(cx.executor());
13846    fs.insert_tree(
13847        path!("/a"),
13848        json!({
13849            "main.rs": buffer_text,
13850        }),
13851    )
13852    .await;
13853
13854    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13855    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13856    language_registry.add(rust_lang());
13857    let mut fake_servers = language_registry.register_fake_lsp(
13858        "Rust",
13859        FakeLspAdapter {
13860            capabilities: lsp::ServerCapabilities {
13861                completion_provider: Some(lsp::CompletionOptions {
13862                    resolve_provider: None,
13863                    ..lsp::CompletionOptions::default()
13864                }),
13865                ..lsp::ServerCapabilities::default()
13866            },
13867            ..FakeLspAdapter::default()
13868        },
13869    );
13870    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13871    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13872    let buffer = project
13873        .update(cx, |project, cx| {
13874            project.open_local_buffer(path!("/a/main.rs"), cx)
13875        })
13876        .await
13877        .unwrap();
13878
13879    let multi_buffer = cx.new(|cx| {
13880        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13881        multi_buffer.push_excerpts(
13882            buffer.clone(),
13883            [ExcerptRange::new(0..first_excerpt_end)],
13884            cx,
13885        );
13886        multi_buffer.push_excerpts(
13887            buffer.clone(),
13888            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13889            cx,
13890        );
13891        multi_buffer
13892    });
13893
13894    let editor = workspace
13895        .update(cx, |_, window, cx| {
13896            cx.new(|cx| {
13897                Editor::new(
13898                    EditorMode::Full {
13899                        scale_ui_elements_with_buffer_font_size: false,
13900                        show_active_line_background: false,
13901                        sized_by_content: false,
13902                    },
13903                    multi_buffer.clone(),
13904                    Some(project.clone()),
13905                    window,
13906                    cx,
13907                )
13908            })
13909        })
13910        .unwrap();
13911
13912    let pane = workspace
13913        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13914        .unwrap();
13915    pane.update_in(cx, |pane, window, cx| {
13916        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13917    });
13918
13919    let fake_server = fake_servers.next().await.unwrap();
13920
13921    editor.update_in(cx, |editor, window, cx| {
13922        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13923            s.select_ranges([
13924                Point::new(1, 11)..Point::new(1, 11),
13925                Point::new(7, 11)..Point::new(7, 11),
13926            ])
13927        });
13928
13929        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13930    });
13931
13932    editor.update_in(cx, |editor, window, cx| {
13933        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13934    });
13935
13936    fake_server
13937        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13938            let completion_item = lsp::CompletionItem {
13939                label: "saturating_sub()".into(),
13940                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13941                    lsp::InsertReplaceEdit {
13942                        new_text: "saturating_sub()".to_owned(),
13943                        insert: lsp::Range::new(
13944                            lsp::Position::new(7, 7),
13945                            lsp::Position::new(7, 11),
13946                        ),
13947                        replace: lsp::Range::new(
13948                            lsp::Position::new(7, 7),
13949                            lsp::Position::new(7, 13),
13950                        ),
13951                    },
13952                )),
13953                ..lsp::CompletionItem::default()
13954            };
13955
13956            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13957        })
13958        .next()
13959        .await
13960        .unwrap();
13961
13962    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13963        .await;
13964
13965    editor
13966        .update_in(cx, |editor, window, cx| {
13967            editor
13968                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13969                .unwrap()
13970        })
13971        .await
13972        .unwrap();
13973
13974    editor.update(cx, |editor, cx| {
13975        assert_text_with_selections(editor, expected_multibuffer, cx);
13976    })
13977}
13978
13979#[gpui::test]
13980async fn test_completion(cx: &mut TestAppContext) {
13981    init_test(cx, |_| {});
13982
13983    let mut cx = EditorLspTestContext::new_rust(
13984        lsp::ServerCapabilities {
13985            completion_provider: Some(lsp::CompletionOptions {
13986                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13987                resolve_provider: Some(true),
13988                ..Default::default()
13989            }),
13990            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13991            ..Default::default()
13992        },
13993        cx,
13994    )
13995    .await;
13996    let counter = Arc::new(AtomicUsize::new(0));
13997
13998    cx.set_state(indoc! {"
13999        oneˇ
14000        two
14001        three
14002    "});
14003    cx.simulate_keystroke(".");
14004    handle_completion_request(
14005        indoc! {"
14006            one.|<>
14007            two
14008            three
14009        "},
14010        vec!["first_completion", "second_completion"],
14011        true,
14012        counter.clone(),
14013        &mut cx,
14014    )
14015    .await;
14016    cx.condition(|editor, _| editor.context_menu_visible())
14017        .await;
14018    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14019
14020    let _handler = handle_signature_help_request(
14021        &mut cx,
14022        lsp::SignatureHelp {
14023            signatures: vec![lsp::SignatureInformation {
14024                label: "test signature".to_string(),
14025                documentation: None,
14026                parameters: Some(vec![lsp::ParameterInformation {
14027                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14028                    documentation: None,
14029                }]),
14030                active_parameter: None,
14031            }],
14032            active_signature: None,
14033            active_parameter: None,
14034        },
14035    );
14036    cx.update_editor(|editor, window, cx| {
14037        assert!(
14038            !editor.signature_help_state.is_shown(),
14039            "No signature help was called for"
14040        );
14041        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14042    });
14043    cx.run_until_parked();
14044    cx.update_editor(|editor, _, _| {
14045        assert!(
14046            !editor.signature_help_state.is_shown(),
14047            "No signature help should be shown when completions menu is open"
14048        );
14049    });
14050
14051    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14052        editor.context_menu_next(&Default::default(), window, cx);
14053        editor
14054            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14055            .unwrap()
14056    });
14057    cx.assert_editor_state(indoc! {"
14058        one.second_completionˇ
14059        two
14060        three
14061    "});
14062
14063    handle_resolve_completion_request(
14064        &mut cx,
14065        Some(vec![
14066            (
14067                //This overlaps with the primary completion edit which is
14068                //misbehavior from the LSP spec, test that we filter it out
14069                indoc! {"
14070                    one.second_ˇcompletion
14071                    two
14072                    threeˇ
14073                "},
14074                "overlapping additional edit",
14075            ),
14076            (
14077                indoc! {"
14078                    one.second_completion
14079                    two
14080                    threeˇ
14081                "},
14082                "\nadditional edit",
14083            ),
14084        ]),
14085    )
14086    .await;
14087    apply_additional_edits.await.unwrap();
14088    cx.assert_editor_state(indoc! {"
14089        one.second_completionˇ
14090        two
14091        three
14092        additional edit
14093    "});
14094
14095    cx.set_state(indoc! {"
14096        one.second_completion
14097        twoˇ
14098        threeˇ
14099        additional edit
14100    "});
14101    cx.simulate_keystroke(" ");
14102    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14103    cx.simulate_keystroke("s");
14104    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14105
14106    cx.assert_editor_state(indoc! {"
14107        one.second_completion
14108        two sˇ
14109        three sˇ
14110        additional edit
14111    "});
14112    handle_completion_request(
14113        indoc! {"
14114            one.second_completion
14115            two s
14116            three <s|>
14117            additional edit
14118        "},
14119        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14120        true,
14121        counter.clone(),
14122        &mut cx,
14123    )
14124    .await;
14125    cx.condition(|editor, _| editor.context_menu_visible())
14126        .await;
14127    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14128
14129    cx.simulate_keystroke("i");
14130
14131    handle_completion_request(
14132        indoc! {"
14133            one.second_completion
14134            two si
14135            three <si|>
14136            additional edit
14137        "},
14138        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14139        true,
14140        counter.clone(),
14141        &mut cx,
14142    )
14143    .await;
14144    cx.condition(|editor, _| editor.context_menu_visible())
14145        .await;
14146    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14147
14148    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14149        editor
14150            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14151            .unwrap()
14152    });
14153    cx.assert_editor_state(indoc! {"
14154        one.second_completion
14155        two sixth_completionˇ
14156        three sixth_completionˇ
14157        additional edit
14158    "});
14159
14160    apply_additional_edits.await.unwrap();
14161
14162    update_test_language_settings(&mut cx, |settings| {
14163        settings.defaults.show_completions_on_input = Some(false);
14164    });
14165    cx.set_state("editorˇ");
14166    cx.simulate_keystroke(".");
14167    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14168    cx.simulate_keystrokes("c l o");
14169    cx.assert_editor_state("editor.cloˇ");
14170    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14171    cx.update_editor(|editor, window, cx| {
14172        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14173    });
14174    handle_completion_request(
14175        "editor.<clo|>",
14176        vec!["close", "clobber"],
14177        true,
14178        counter.clone(),
14179        &mut cx,
14180    )
14181    .await;
14182    cx.condition(|editor, _| editor.context_menu_visible())
14183        .await;
14184    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14185
14186    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14187        editor
14188            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14189            .unwrap()
14190    });
14191    cx.assert_editor_state("editor.clobberˇ");
14192    handle_resolve_completion_request(&mut cx, None).await;
14193    apply_additional_edits.await.unwrap();
14194}
14195
14196#[gpui::test]
14197async fn test_completion_reuse(cx: &mut TestAppContext) {
14198    init_test(cx, |_| {});
14199
14200    let mut cx = EditorLspTestContext::new_rust(
14201        lsp::ServerCapabilities {
14202            completion_provider: Some(lsp::CompletionOptions {
14203                trigger_characters: Some(vec![".".to_string()]),
14204                ..Default::default()
14205            }),
14206            ..Default::default()
14207        },
14208        cx,
14209    )
14210    .await;
14211
14212    let counter = Arc::new(AtomicUsize::new(0));
14213    cx.set_state("objˇ");
14214    cx.simulate_keystroke(".");
14215
14216    // Initial completion request returns complete results
14217    let is_incomplete = false;
14218    handle_completion_request(
14219        "obj.|<>",
14220        vec!["a", "ab", "abc"],
14221        is_incomplete,
14222        counter.clone(),
14223        &mut cx,
14224    )
14225    .await;
14226    cx.run_until_parked();
14227    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14228    cx.assert_editor_state("obj.ˇ");
14229    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14230
14231    // Type "a" - filters existing completions
14232    cx.simulate_keystroke("a");
14233    cx.run_until_parked();
14234    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14235    cx.assert_editor_state("obj.aˇ");
14236    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14237
14238    // Type "b" - filters existing completions
14239    cx.simulate_keystroke("b");
14240    cx.run_until_parked();
14241    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14242    cx.assert_editor_state("obj.abˇ");
14243    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14244
14245    // Type "c" - filters existing completions
14246    cx.simulate_keystroke("c");
14247    cx.run_until_parked();
14248    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14249    cx.assert_editor_state("obj.abcˇ");
14250    check_displayed_completions(vec!["abc"], &mut cx);
14251
14252    // Backspace to delete "c" - filters existing completions
14253    cx.update_editor(|editor, window, cx| {
14254        editor.backspace(&Backspace, window, cx);
14255    });
14256    cx.run_until_parked();
14257    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14258    cx.assert_editor_state("obj.abˇ");
14259    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14260
14261    // Moving cursor to the left dismisses menu.
14262    cx.update_editor(|editor, window, cx| {
14263        editor.move_left(&MoveLeft, window, cx);
14264    });
14265    cx.run_until_parked();
14266    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14267    cx.assert_editor_state("obj.aˇb");
14268    cx.update_editor(|editor, _, _| {
14269        assert_eq!(editor.context_menu_visible(), false);
14270    });
14271
14272    // Type "b" - new request
14273    cx.simulate_keystroke("b");
14274    let is_incomplete = false;
14275    handle_completion_request(
14276        "obj.<ab|>a",
14277        vec!["ab", "abc"],
14278        is_incomplete,
14279        counter.clone(),
14280        &mut cx,
14281    )
14282    .await;
14283    cx.run_until_parked();
14284    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14285    cx.assert_editor_state("obj.abˇb");
14286    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14287
14288    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14289    cx.update_editor(|editor, window, cx| {
14290        editor.backspace(&Backspace, window, cx);
14291    });
14292    let is_incomplete = false;
14293    handle_completion_request(
14294        "obj.<a|>b",
14295        vec!["a", "ab", "abc"],
14296        is_incomplete,
14297        counter.clone(),
14298        &mut cx,
14299    )
14300    .await;
14301    cx.run_until_parked();
14302    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14303    cx.assert_editor_state("obj.aˇb");
14304    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14305
14306    // Backspace to delete "a" - dismisses menu.
14307    cx.update_editor(|editor, window, cx| {
14308        editor.backspace(&Backspace, window, cx);
14309    });
14310    cx.run_until_parked();
14311    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14312    cx.assert_editor_state("obj.ˇb");
14313    cx.update_editor(|editor, _, _| {
14314        assert_eq!(editor.context_menu_visible(), false);
14315    });
14316}
14317
14318#[gpui::test]
14319async fn test_word_completion(cx: &mut TestAppContext) {
14320    let lsp_fetch_timeout_ms = 10;
14321    init_test(cx, |language_settings| {
14322        language_settings.defaults.completions = Some(CompletionSettingsContent {
14323            words_min_length: Some(0),
14324            lsp_fetch_timeout_ms: Some(10),
14325            lsp_insert_mode: Some(LspInsertMode::Insert),
14326            ..Default::default()
14327        });
14328    });
14329
14330    let mut cx = EditorLspTestContext::new_rust(
14331        lsp::ServerCapabilities {
14332            completion_provider: Some(lsp::CompletionOptions {
14333                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14334                ..lsp::CompletionOptions::default()
14335            }),
14336            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14337            ..lsp::ServerCapabilities::default()
14338        },
14339        cx,
14340    )
14341    .await;
14342
14343    let throttle_completions = Arc::new(AtomicBool::new(false));
14344
14345    let lsp_throttle_completions = throttle_completions.clone();
14346    let _completion_requests_handler =
14347        cx.lsp
14348            .server
14349            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14350                let lsp_throttle_completions = lsp_throttle_completions.clone();
14351                let cx = cx.clone();
14352                async move {
14353                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14354                        cx.background_executor()
14355                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14356                            .await;
14357                    }
14358                    Ok(Some(lsp::CompletionResponse::Array(vec![
14359                        lsp::CompletionItem {
14360                            label: "first".into(),
14361                            ..lsp::CompletionItem::default()
14362                        },
14363                        lsp::CompletionItem {
14364                            label: "last".into(),
14365                            ..lsp::CompletionItem::default()
14366                        },
14367                    ])))
14368                }
14369            });
14370
14371    cx.set_state(indoc! {"
14372        oneˇ
14373        two
14374        three
14375    "});
14376    cx.simulate_keystroke(".");
14377    cx.executor().run_until_parked();
14378    cx.condition(|editor, _| editor.context_menu_visible())
14379        .await;
14380    cx.update_editor(|editor, window, cx| {
14381        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14382        {
14383            assert_eq!(
14384                completion_menu_entries(menu),
14385                &["first", "last"],
14386                "When LSP server is fast to reply, no fallback word completions are used"
14387            );
14388        } else {
14389            panic!("expected completion menu to be open");
14390        }
14391        editor.cancel(&Cancel, window, cx);
14392    });
14393    cx.executor().run_until_parked();
14394    cx.condition(|editor, _| !editor.context_menu_visible())
14395        .await;
14396
14397    throttle_completions.store(true, atomic::Ordering::Release);
14398    cx.simulate_keystroke(".");
14399    cx.executor()
14400        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14401    cx.executor().run_until_parked();
14402    cx.condition(|editor, _| editor.context_menu_visible())
14403        .await;
14404    cx.update_editor(|editor, _, _| {
14405        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14406        {
14407            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14408                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14409        } else {
14410            panic!("expected completion menu to be open");
14411        }
14412    });
14413}
14414
14415#[gpui::test]
14416async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14417    init_test(cx, |language_settings| {
14418        language_settings.defaults.completions = Some(CompletionSettingsContent {
14419            words: Some(WordsCompletionMode::Enabled),
14420            words_min_length: Some(0),
14421            lsp_insert_mode: Some(LspInsertMode::Insert),
14422            ..Default::default()
14423        });
14424    });
14425
14426    let mut cx = EditorLspTestContext::new_rust(
14427        lsp::ServerCapabilities {
14428            completion_provider: Some(lsp::CompletionOptions {
14429                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14430                ..lsp::CompletionOptions::default()
14431            }),
14432            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14433            ..lsp::ServerCapabilities::default()
14434        },
14435        cx,
14436    )
14437    .await;
14438
14439    let _completion_requests_handler =
14440        cx.lsp
14441            .server
14442            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14443                Ok(Some(lsp::CompletionResponse::Array(vec![
14444                    lsp::CompletionItem {
14445                        label: "first".into(),
14446                        ..lsp::CompletionItem::default()
14447                    },
14448                    lsp::CompletionItem {
14449                        label: "last".into(),
14450                        ..lsp::CompletionItem::default()
14451                    },
14452                ])))
14453            });
14454
14455    cx.set_state(indoc! {"ˇ
14456        first
14457        last
14458        second
14459    "});
14460    cx.simulate_keystroke(".");
14461    cx.executor().run_until_parked();
14462    cx.condition(|editor, _| editor.context_menu_visible())
14463        .await;
14464    cx.update_editor(|editor, _, _| {
14465        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14466        {
14467            assert_eq!(
14468                completion_menu_entries(menu),
14469                &["first", "last", "second"],
14470                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14471            );
14472        } else {
14473            panic!("expected completion menu to be open");
14474        }
14475    });
14476}
14477
14478#[gpui::test]
14479async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14480    init_test(cx, |language_settings| {
14481        language_settings.defaults.completions = Some(CompletionSettingsContent {
14482            words: Some(WordsCompletionMode::Disabled),
14483            words_min_length: Some(0),
14484            lsp_insert_mode: Some(LspInsertMode::Insert),
14485            ..Default::default()
14486        });
14487    });
14488
14489    let mut cx = EditorLspTestContext::new_rust(
14490        lsp::ServerCapabilities {
14491            completion_provider: Some(lsp::CompletionOptions {
14492                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14493                ..lsp::CompletionOptions::default()
14494            }),
14495            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14496            ..lsp::ServerCapabilities::default()
14497        },
14498        cx,
14499    )
14500    .await;
14501
14502    let _completion_requests_handler =
14503        cx.lsp
14504            .server
14505            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14506                panic!("LSP completions should not be queried when dealing with word completions")
14507            });
14508
14509    cx.set_state(indoc! {"ˇ
14510        first
14511        last
14512        second
14513    "});
14514    cx.update_editor(|editor, window, cx| {
14515        editor.show_word_completions(&ShowWordCompletions, window, cx);
14516    });
14517    cx.executor().run_until_parked();
14518    cx.condition(|editor, _| editor.context_menu_visible())
14519        .await;
14520    cx.update_editor(|editor, _, _| {
14521        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14522        {
14523            assert_eq!(
14524                completion_menu_entries(menu),
14525                &["first", "last", "second"],
14526                "`ShowWordCompletions` action should show word completions"
14527            );
14528        } else {
14529            panic!("expected completion menu to be open");
14530        }
14531    });
14532
14533    cx.simulate_keystroke("l");
14534    cx.executor().run_until_parked();
14535    cx.condition(|editor, _| editor.context_menu_visible())
14536        .await;
14537    cx.update_editor(|editor, _, _| {
14538        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14539        {
14540            assert_eq!(
14541                completion_menu_entries(menu),
14542                &["last"],
14543                "After showing word completions, further editing should filter them and not query the LSP"
14544            );
14545        } else {
14546            panic!("expected completion menu to be open");
14547        }
14548    });
14549}
14550
14551#[gpui::test]
14552async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14553    init_test(cx, |language_settings| {
14554        language_settings.defaults.completions = Some(CompletionSettingsContent {
14555            words_min_length: Some(0),
14556            lsp: Some(false),
14557            lsp_insert_mode: Some(LspInsertMode::Insert),
14558            ..Default::default()
14559        });
14560    });
14561
14562    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14563
14564    cx.set_state(indoc! {"ˇ
14565        0_usize
14566        let
14567        33
14568        4.5f32
14569    "});
14570    cx.update_editor(|editor, window, cx| {
14571        editor.show_completions(&ShowCompletions::default(), window, cx);
14572    });
14573    cx.executor().run_until_parked();
14574    cx.condition(|editor, _| editor.context_menu_visible())
14575        .await;
14576    cx.update_editor(|editor, window, cx| {
14577        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14578        {
14579            assert_eq!(
14580                completion_menu_entries(menu),
14581                &["let"],
14582                "With no digits in the completion query, no digits should be in the word completions"
14583            );
14584        } else {
14585            panic!("expected completion menu to be open");
14586        }
14587        editor.cancel(&Cancel, window, cx);
14588    });
14589
14590    cx.set_state(indoc! {"14591        0_usize
14592        let
14593        3
14594        33.35f32
14595    "});
14596    cx.update_editor(|editor, window, cx| {
14597        editor.show_completions(&ShowCompletions::default(), window, cx);
14598    });
14599    cx.executor().run_until_parked();
14600    cx.condition(|editor, _| editor.context_menu_visible())
14601        .await;
14602    cx.update_editor(|editor, _, _| {
14603        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14604        {
14605            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14606                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14607        } else {
14608            panic!("expected completion menu to be open");
14609        }
14610    });
14611}
14612
14613#[gpui::test]
14614async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14615    init_test(cx, |language_settings| {
14616        language_settings.defaults.completions = Some(CompletionSettingsContent {
14617            words: Some(WordsCompletionMode::Enabled),
14618            words_min_length: Some(3),
14619            lsp_insert_mode: Some(LspInsertMode::Insert),
14620            ..Default::default()
14621        });
14622    });
14623
14624    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14625    cx.set_state(indoc! {"ˇ
14626        wow
14627        wowen
14628        wowser
14629    "});
14630    cx.simulate_keystroke("w");
14631    cx.executor().run_until_parked();
14632    cx.update_editor(|editor, _, _| {
14633        if editor.context_menu.borrow_mut().is_some() {
14634            panic!(
14635                "expected completion menu to be hidden, as words completion threshold is not met"
14636            );
14637        }
14638    });
14639
14640    cx.update_editor(|editor, window, cx| {
14641        editor.show_word_completions(&ShowWordCompletions, window, cx);
14642    });
14643    cx.executor().run_until_parked();
14644    cx.update_editor(|editor, window, cx| {
14645        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14646        {
14647            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");
14648        } else {
14649            panic!("expected completion menu to be open after the word completions are called with an action");
14650        }
14651
14652        editor.cancel(&Cancel, window, cx);
14653    });
14654    cx.update_editor(|editor, _, _| {
14655        if editor.context_menu.borrow_mut().is_some() {
14656            panic!("expected completion menu to be hidden after canceling");
14657        }
14658    });
14659
14660    cx.simulate_keystroke("o");
14661    cx.executor().run_until_parked();
14662    cx.update_editor(|editor, _, _| {
14663        if editor.context_menu.borrow_mut().is_some() {
14664            panic!(
14665                "expected completion menu to be hidden, as words completion threshold is not met still"
14666            );
14667        }
14668    });
14669
14670    cx.simulate_keystroke("w");
14671    cx.executor().run_until_parked();
14672    cx.update_editor(|editor, _, _| {
14673        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14674        {
14675            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14676        } else {
14677            panic!("expected completion menu to be open after the word completions threshold is met");
14678        }
14679    });
14680}
14681
14682#[gpui::test]
14683async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14684    init_test(cx, |language_settings| {
14685        language_settings.defaults.completions = Some(CompletionSettingsContent {
14686            words: Some(WordsCompletionMode::Enabled),
14687            words_min_length: Some(0),
14688            lsp_insert_mode: Some(LspInsertMode::Insert),
14689            ..Default::default()
14690        });
14691    });
14692
14693    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14694    cx.update_editor(|editor, _, _| {
14695        editor.disable_word_completions();
14696    });
14697    cx.set_state(indoc! {"ˇ
14698        wow
14699        wowen
14700        wowser
14701    "});
14702    cx.simulate_keystroke("w");
14703    cx.executor().run_until_parked();
14704    cx.update_editor(|editor, _, _| {
14705        if editor.context_menu.borrow_mut().is_some() {
14706            panic!(
14707                "expected completion menu to be hidden, as words completion are disabled for this editor"
14708            );
14709        }
14710    });
14711
14712    cx.update_editor(|editor, window, cx| {
14713        editor.show_word_completions(&ShowWordCompletions, window, cx);
14714    });
14715    cx.executor().run_until_parked();
14716    cx.update_editor(|editor, _, _| {
14717        if editor.context_menu.borrow_mut().is_some() {
14718            panic!(
14719                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14720            );
14721        }
14722    });
14723}
14724
14725fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14726    let position = || lsp::Position {
14727        line: params.text_document_position.position.line,
14728        character: params.text_document_position.position.character,
14729    };
14730    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14731        range: lsp::Range {
14732            start: position(),
14733            end: position(),
14734        },
14735        new_text: text.to_string(),
14736    }))
14737}
14738
14739#[gpui::test]
14740async fn test_multiline_completion(cx: &mut TestAppContext) {
14741    init_test(cx, |_| {});
14742
14743    let fs = FakeFs::new(cx.executor());
14744    fs.insert_tree(
14745        path!("/a"),
14746        json!({
14747            "main.ts": "a",
14748        }),
14749    )
14750    .await;
14751
14752    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14753    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14754    let typescript_language = Arc::new(Language::new(
14755        LanguageConfig {
14756            name: "TypeScript".into(),
14757            matcher: LanguageMatcher {
14758                path_suffixes: vec!["ts".to_string()],
14759                ..LanguageMatcher::default()
14760            },
14761            line_comments: vec!["// ".into()],
14762            ..LanguageConfig::default()
14763        },
14764        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14765    ));
14766    language_registry.add(typescript_language.clone());
14767    let mut fake_servers = language_registry.register_fake_lsp(
14768        "TypeScript",
14769        FakeLspAdapter {
14770            capabilities: lsp::ServerCapabilities {
14771                completion_provider: Some(lsp::CompletionOptions {
14772                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14773                    ..lsp::CompletionOptions::default()
14774                }),
14775                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14776                ..lsp::ServerCapabilities::default()
14777            },
14778            // Emulate vtsls label generation
14779            label_for_completion: Some(Box::new(|item, _| {
14780                let text = if let Some(description) = item
14781                    .label_details
14782                    .as_ref()
14783                    .and_then(|label_details| label_details.description.as_ref())
14784                {
14785                    format!("{} {}", item.label, description)
14786                } else if let Some(detail) = &item.detail {
14787                    format!("{} {}", item.label, detail)
14788                } else {
14789                    item.label.clone()
14790                };
14791                let len = text.len();
14792                Some(language::CodeLabel {
14793                    text,
14794                    runs: Vec::new(),
14795                    filter_range: 0..len,
14796                })
14797            })),
14798            ..FakeLspAdapter::default()
14799        },
14800    );
14801    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14802    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14803    let worktree_id = workspace
14804        .update(cx, |workspace, _window, cx| {
14805            workspace.project().update(cx, |project, cx| {
14806                project.worktrees(cx).next().unwrap().read(cx).id()
14807            })
14808        })
14809        .unwrap();
14810    let _buffer = project
14811        .update(cx, |project, cx| {
14812            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14813        })
14814        .await
14815        .unwrap();
14816    let editor = workspace
14817        .update(cx, |workspace, window, cx| {
14818            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
14819        })
14820        .unwrap()
14821        .await
14822        .unwrap()
14823        .downcast::<Editor>()
14824        .unwrap();
14825    let fake_server = fake_servers.next().await.unwrap();
14826
14827    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14828    let multiline_label_2 = "a\nb\nc\n";
14829    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14830    let multiline_description = "d\ne\nf\n";
14831    let multiline_detail_2 = "g\nh\ni\n";
14832
14833    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14834        move |params, _| async move {
14835            Ok(Some(lsp::CompletionResponse::Array(vec![
14836                lsp::CompletionItem {
14837                    label: multiline_label.to_string(),
14838                    text_edit: gen_text_edit(&params, "new_text_1"),
14839                    ..lsp::CompletionItem::default()
14840                },
14841                lsp::CompletionItem {
14842                    label: "single line label 1".to_string(),
14843                    detail: Some(multiline_detail.to_string()),
14844                    text_edit: gen_text_edit(&params, "new_text_2"),
14845                    ..lsp::CompletionItem::default()
14846                },
14847                lsp::CompletionItem {
14848                    label: "single line label 2".to_string(),
14849                    label_details: Some(lsp::CompletionItemLabelDetails {
14850                        description: Some(multiline_description.to_string()),
14851                        detail: None,
14852                    }),
14853                    text_edit: gen_text_edit(&params, "new_text_2"),
14854                    ..lsp::CompletionItem::default()
14855                },
14856                lsp::CompletionItem {
14857                    label: multiline_label_2.to_string(),
14858                    detail: Some(multiline_detail_2.to_string()),
14859                    text_edit: gen_text_edit(&params, "new_text_3"),
14860                    ..lsp::CompletionItem::default()
14861                },
14862                lsp::CompletionItem {
14863                    label: "Label with many     spaces and \t but without newlines".to_string(),
14864                    detail: Some(
14865                        "Details with many     spaces and \t but without newlines".to_string(),
14866                    ),
14867                    text_edit: gen_text_edit(&params, "new_text_4"),
14868                    ..lsp::CompletionItem::default()
14869                },
14870            ])))
14871        },
14872    );
14873
14874    editor.update_in(cx, |editor, window, cx| {
14875        cx.focus_self(window);
14876        editor.move_to_end(&MoveToEnd, window, cx);
14877        editor.handle_input(".", window, cx);
14878    });
14879    cx.run_until_parked();
14880    completion_handle.next().await.unwrap();
14881
14882    editor.update(cx, |editor, _| {
14883        assert!(editor.context_menu_visible());
14884        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14885        {
14886            let completion_labels = menu
14887                .completions
14888                .borrow()
14889                .iter()
14890                .map(|c| c.label.text.clone())
14891                .collect::<Vec<_>>();
14892            assert_eq!(
14893                completion_labels,
14894                &[
14895                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14896                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14897                    "single line label 2 d e f ",
14898                    "a b c g h i ",
14899                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14900                ],
14901                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14902            );
14903
14904            for completion in menu
14905                .completions
14906                .borrow()
14907                .iter() {
14908                    assert_eq!(
14909                        completion.label.filter_range,
14910                        0..completion.label.text.len(),
14911                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14912                    );
14913                }
14914        } else {
14915            panic!("expected completion menu to be open");
14916        }
14917    });
14918}
14919
14920#[gpui::test]
14921async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14922    init_test(cx, |_| {});
14923    let mut cx = EditorLspTestContext::new_rust(
14924        lsp::ServerCapabilities {
14925            completion_provider: Some(lsp::CompletionOptions {
14926                trigger_characters: Some(vec![".".to_string()]),
14927                ..Default::default()
14928            }),
14929            ..Default::default()
14930        },
14931        cx,
14932    )
14933    .await;
14934    cx.lsp
14935        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14936            Ok(Some(lsp::CompletionResponse::Array(vec![
14937                lsp::CompletionItem {
14938                    label: "first".into(),
14939                    ..Default::default()
14940                },
14941                lsp::CompletionItem {
14942                    label: "last".into(),
14943                    ..Default::default()
14944                },
14945            ])))
14946        });
14947    cx.set_state("variableˇ");
14948    cx.simulate_keystroke(".");
14949    cx.executor().run_until_parked();
14950
14951    cx.update_editor(|editor, _, _| {
14952        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14953        {
14954            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14955        } else {
14956            panic!("expected completion menu to be open");
14957        }
14958    });
14959
14960    cx.update_editor(|editor, window, cx| {
14961        editor.move_page_down(&MovePageDown::default(), window, cx);
14962        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14963        {
14964            assert!(
14965                menu.selected_item == 1,
14966                "expected PageDown to select the last item from the context menu"
14967            );
14968        } else {
14969            panic!("expected completion menu to stay open after PageDown");
14970        }
14971    });
14972
14973    cx.update_editor(|editor, window, cx| {
14974        editor.move_page_up(&MovePageUp::default(), window, cx);
14975        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14976        {
14977            assert!(
14978                menu.selected_item == 0,
14979                "expected PageUp to select the first item from the context menu"
14980            );
14981        } else {
14982            panic!("expected completion menu to stay open after PageUp");
14983        }
14984    });
14985}
14986
14987#[gpui::test]
14988async fn test_as_is_completions(cx: &mut TestAppContext) {
14989    init_test(cx, |_| {});
14990    let mut cx = EditorLspTestContext::new_rust(
14991        lsp::ServerCapabilities {
14992            completion_provider: Some(lsp::CompletionOptions {
14993                ..Default::default()
14994            }),
14995            ..Default::default()
14996        },
14997        cx,
14998    )
14999    .await;
15000    cx.lsp
15001        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15002            Ok(Some(lsp::CompletionResponse::Array(vec![
15003                lsp::CompletionItem {
15004                    label: "unsafe".into(),
15005                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15006                        range: lsp::Range {
15007                            start: lsp::Position {
15008                                line: 1,
15009                                character: 2,
15010                            },
15011                            end: lsp::Position {
15012                                line: 1,
15013                                character: 3,
15014                            },
15015                        },
15016                        new_text: "unsafe".to_string(),
15017                    })),
15018                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15019                    ..Default::default()
15020                },
15021            ])))
15022        });
15023    cx.set_state("fn a() {}\n");
15024    cx.executor().run_until_parked();
15025    cx.update_editor(|editor, window, cx| {
15026        editor.show_completions(
15027            &ShowCompletions {
15028                trigger: Some("\n".into()),
15029            },
15030            window,
15031            cx,
15032        );
15033    });
15034    cx.executor().run_until_parked();
15035
15036    cx.update_editor(|editor, window, cx| {
15037        editor.confirm_completion(&Default::default(), window, cx)
15038    });
15039    cx.executor().run_until_parked();
15040    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15041}
15042
15043#[gpui::test]
15044async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15045    init_test(cx, |_| {});
15046    let language =
15047        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15048    let mut cx = EditorLspTestContext::new(
15049        language,
15050        lsp::ServerCapabilities {
15051            completion_provider: Some(lsp::CompletionOptions {
15052                ..lsp::CompletionOptions::default()
15053            }),
15054            ..lsp::ServerCapabilities::default()
15055        },
15056        cx,
15057    )
15058    .await;
15059
15060    cx.set_state(
15061        "#ifndef BAR_H
15062#define BAR_H
15063
15064#include <stdbool.h>
15065
15066int fn_branch(bool do_branch1, bool do_branch2);
15067
15068#endif // BAR_H
15069ˇ",
15070    );
15071    cx.executor().run_until_parked();
15072    cx.update_editor(|editor, window, cx| {
15073        editor.handle_input("#", window, cx);
15074    });
15075    cx.executor().run_until_parked();
15076    cx.update_editor(|editor, window, cx| {
15077        editor.handle_input("i", window, cx);
15078    });
15079    cx.executor().run_until_parked();
15080    cx.update_editor(|editor, window, cx| {
15081        editor.handle_input("n", window, cx);
15082    });
15083    cx.executor().run_until_parked();
15084    cx.assert_editor_state(
15085        "#ifndef BAR_H
15086#define BAR_H
15087
15088#include <stdbool.h>
15089
15090int fn_branch(bool do_branch1, bool do_branch2);
15091
15092#endif // BAR_H
15093#inˇ",
15094    );
15095
15096    cx.lsp
15097        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15098            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15099                is_incomplete: false,
15100                item_defaults: None,
15101                items: vec![lsp::CompletionItem {
15102                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15103                    label_details: Some(lsp::CompletionItemLabelDetails {
15104                        detail: Some("header".to_string()),
15105                        description: None,
15106                    }),
15107                    label: " include".to_string(),
15108                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15109                        range: lsp::Range {
15110                            start: lsp::Position {
15111                                line: 8,
15112                                character: 1,
15113                            },
15114                            end: lsp::Position {
15115                                line: 8,
15116                                character: 1,
15117                            },
15118                        },
15119                        new_text: "include \"$0\"".to_string(),
15120                    })),
15121                    sort_text: Some("40b67681include".to_string()),
15122                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15123                    filter_text: Some("include".to_string()),
15124                    insert_text: Some("include \"$0\"".to_string()),
15125                    ..lsp::CompletionItem::default()
15126                }],
15127            })))
15128        });
15129    cx.update_editor(|editor, window, cx| {
15130        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15131    });
15132    cx.executor().run_until_parked();
15133    cx.update_editor(|editor, window, cx| {
15134        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15135    });
15136    cx.executor().run_until_parked();
15137    cx.assert_editor_state(
15138        "#ifndef BAR_H
15139#define BAR_H
15140
15141#include <stdbool.h>
15142
15143int fn_branch(bool do_branch1, bool do_branch2);
15144
15145#endif // BAR_H
15146#include \"ˇ\"",
15147    );
15148
15149    cx.lsp
15150        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15151            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15152                is_incomplete: true,
15153                item_defaults: None,
15154                items: vec![lsp::CompletionItem {
15155                    kind: Some(lsp::CompletionItemKind::FILE),
15156                    label: "AGL/".to_string(),
15157                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15158                        range: lsp::Range {
15159                            start: lsp::Position {
15160                                line: 8,
15161                                character: 10,
15162                            },
15163                            end: lsp::Position {
15164                                line: 8,
15165                                character: 11,
15166                            },
15167                        },
15168                        new_text: "AGL/".to_string(),
15169                    })),
15170                    sort_text: Some("40b67681AGL/".to_string()),
15171                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15172                    filter_text: Some("AGL/".to_string()),
15173                    insert_text: Some("AGL/".to_string()),
15174                    ..lsp::CompletionItem::default()
15175                }],
15176            })))
15177        });
15178    cx.update_editor(|editor, window, cx| {
15179        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15180    });
15181    cx.executor().run_until_parked();
15182    cx.update_editor(|editor, window, cx| {
15183        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15184    });
15185    cx.executor().run_until_parked();
15186    cx.assert_editor_state(
15187        r##"#ifndef BAR_H
15188#define BAR_H
15189
15190#include <stdbool.h>
15191
15192int fn_branch(bool do_branch1, bool do_branch2);
15193
15194#endif // BAR_H
15195#include "AGL/ˇ"##,
15196    );
15197
15198    cx.update_editor(|editor, window, cx| {
15199        editor.handle_input("\"", window, cx);
15200    });
15201    cx.executor().run_until_parked();
15202    cx.assert_editor_state(
15203        r##"#ifndef BAR_H
15204#define BAR_H
15205
15206#include <stdbool.h>
15207
15208int fn_branch(bool do_branch1, bool do_branch2);
15209
15210#endif // BAR_H
15211#include "AGL/"ˇ"##,
15212    );
15213}
15214
15215#[gpui::test]
15216async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15217    init_test(cx, |_| {});
15218
15219    let mut cx = EditorLspTestContext::new_rust(
15220        lsp::ServerCapabilities {
15221            completion_provider: Some(lsp::CompletionOptions {
15222                trigger_characters: Some(vec![".".to_string()]),
15223                resolve_provider: Some(true),
15224                ..Default::default()
15225            }),
15226            ..Default::default()
15227        },
15228        cx,
15229    )
15230    .await;
15231
15232    cx.set_state("fn main() { let a = 2ˇ; }");
15233    cx.simulate_keystroke(".");
15234    let completion_item = lsp::CompletionItem {
15235        label: "Some".into(),
15236        kind: Some(lsp::CompletionItemKind::SNIPPET),
15237        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15238        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15239            kind: lsp::MarkupKind::Markdown,
15240            value: "```rust\nSome(2)\n```".to_string(),
15241        })),
15242        deprecated: Some(false),
15243        sort_text: Some("Some".to_string()),
15244        filter_text: Some("Some".to_string()),
15245        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15246        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15247            range: lsp::Range {
15248                start: lsp::Position {
15249                    line: 0,
15250                    character: 22,
15251                },
15252                end: lsp::Position {
15253                    line: 0,
15254                    character: 22,
15255                },
15256            },
15257            new_text: "Some(2)".to_string(),
15258        })),
15259        additional_text_edits: Some(vec![lsp::TextEdit {
15260            range: lsp::Range {
15261                start: lsp::Position {
15262                    line: 0,
15263                    character: 20,
15264                },
15265                end: lsp::Position {
15266                    line: 0,
15267                    character: 22,
15268                },
15269            },
15270            new_text: "".to_string(),
15271        }]),
15272        ..Default::default()
15273    };
15274
15275    let closure_completion_item = completion_item.clone();
15276    let counter = Arc::new(AtomicUsize::new(0));
15277    let counter_clone = counter.clone();
15278    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15279        let task_completion_item = closure_completion_item.clone();
15280        counter_clone.fetch_add(1, atomic::Ordering::Release);
15281        async move {
15282            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15283                is_incomplete: true,
15284                item_defaults: None,
15285                items: vec![task_completion_item],
15286            })))
15287        }
15288    });
15289
15290    cx.condition(|editor, _| editor.context_menu_visible())
15291        .await;
15292    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15293    assert!(request.next().await.is_some());
15294    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15295
15296    cx.simulate_keystrokes("S o m");
15297    cx.condition(|editor, _| editor.context_menu_visible())
15298        .await;
15299    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15300    assert!(request.next().await.is_some());
15301    assert!(request.next().await.is_some());
15302    assert!(request.next().await.is_some());
15303    request.close();
15304    assert!(request.next().await.is_none());
15305    assert_eq!(
15306        counter.load(atomic::Ordering::Acquire),
15307        4,
15308        "With the completions menu open, only one LSP request should happen per input"
15309    );
15310}
15311
15312#[gpui::test]
15313async fn test_toggle_comment(cx: &mut TestAppContext) {
15314    init_test(cx, |_| {});
15315    let mut cx = EditorTestContext::new(cx).await;
15316    let language = Arc::new(Language::new(
15317        LanguageConfig {
15318            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15319            ..Default::default()
15320        },
15321        Some(tree_sitter_rust::LANGUAGE.into()),
15322    ));
15323    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15324
15325    // If multiple selections intersect a line, the line is only toggled once.
15326    cx.set_state(indoc! {"
15327        fn a() {
15328            «//b();
15329            ˇ»// «c();
15330            //ˇ»  d();
15331        }
15332    "});
15333
15334    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15335
15336    cx.assert_editor_state(indoc! {"
15337        fn a() {
15338            «b();
15339            c();
15340            ˇ» d();
15341        }
15342    "});
15343
15344    // The comment prefix is inserted at the same column for every line in a
15345    // selection.
15346    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15347
15348    cx.assert_editor_state(indoc! {"
15349        fn a() {
15350            // «b();
15351            // c();
15352            ˇ»//  d();
15353        }
15354    "});
15355
15356    // If a selection ends at the beginning of a line, that line is not toggled.
15357    cx.set_selections_state(indoc! {"
15358        fn a() {
15359            // b();
15360            «// c();
15361        ˇ»    //  d();
15362        }
15363    "});
15364
15365    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15366
15367    cx.assert_editor_state(indoc! {"
15368        fn a() {
15369            // b();
15370            «c();
15371        ˇ»    //  d();
15372        }
15373    "});
15374
15375    // If a selection span a single line and is empty, the line is toggled.
15376    cx.set_state(indoc! {"
15377        fn a() {
15378            a();
15379            b();
15380        ˇ
15381        }
15382    "});
15383
15384    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15385
15386    cx.assert_editor_state(indoc! {"
15387        fn a() {
15388            a();
15389            b();
15390        //•ˇ
15391        }
15392    "});
15393
15394    // If a selection span multiple lines, empty lines are not toggled.
15395    cx.set_state(indoc! {"
15396        fn a() {
15397            «a();
15398
15399            c();ˇ»
15400        }
15401    "});
15402
15403    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15404
15405    cx.assert_editor_state(indoc! {"
15406        fn a() {
15407            // «a();
15408
15409            // c();ˇ»
15410        }
15411    "});
15412
15413    // If a selection includes multiple comment prefixes, all lines are uncommented.
15414    cx.set_state(indoc! {"
15415        fn a() {
15416            «// a();
15417            /// b();
15418            //! c();ˇ»
15419        }
15420    "});
15421
15422    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15423
15424    cx.assert_editor_state(indoc! {"
15425        fn a() {
15426            «a();
15427            b();
15428            c();ˇ»
15429        }
15430    "});
15431}
15432
15433#[gpui::test]
15434async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15435    init_test(cx, |_| {});
15436    let mut cx = EditorTestContext::new(cx).await;
15437    let language = Arc::new(Language::new(
15438        LanguageConfig {
15439            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15440            ..Default::default()
15441        },
15442        Some(tree_sitter_rust::LANGUAGE.into()),
15443    ));
15444    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15445
15446    let toggle_comments = &ToggleComments {
15447        advance_downwards: false,
15448        ignore_indent: true,
15449    };
15450
15451    // If multiple selections intersect a line, the line is only toggled once.
15452    cx.set_state(indoc! {"
15453        fn a() {
15454        //    «b();
15455        //    c();
15456        //    ˇ» d();
15457        }
15458    "});
15459
15460    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15461
15462    cx.assert_editor_state(indoc! {"
15463        fn a() {
15464            «b();
15465            c();
15466            ˇ» d();
15467        }
15468    "});
15469
15470    // The comment prefix is inserted at the beginning of each line
15471    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15472
15473    cx.assert_editor_state(indoc! {"
15474        fn a() {
15475        //    «b();
15476        //    c();
15477        //    ˇ» d();
15478        }
15479    "});
15480
15481    // If a selection ends at the beginning of a line, that line is not toggled.
15482    cx.set_selections_state(indoc! {"
15483        fn a() {
15484        //    b();
15485        //    «c();
15486        ˇ»//     d();
15487        }
15488    "});
15489
15490    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15491
15492    cx.assert_editor_state(indoc! {"
15493        fn a() {
15494        //    b();
15495            «c();
15496        ˇ»//     d();
15497        }
15498    "});
15499
15500    // If a selection span a single line and is empty, the line is toggled.
15501    cx.set_state(indoc! {"
15502        fn a() {
15503            a();
15504            b();
15505        ˇ
15506        }
15507    "});
15508
15509    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15510
15511    cx.assert_editor_state(indoc! {"
15512        fn a() {
15513            a();
15514            b();
15515        //ˇ
15516        }
15517    "});
15518
15519    // If a selection span multiple lines, empty lines are not toggled.
15520    cx.set_state(indoc! {"
15521        fn a() {
15522            «a();
15523
15524            c();ˇ»
15525        }
15526    "});
15527
15528    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15529
15530    cx.assert_editor_state(indoc! {"
15531        fn a() {
15532        //    «a();
15533
15534        //    c();ˇ»
15535        }
15536    "});
15537
15538    // If a selection includes multiple comment prefixes, all lines are uncommented.
15539    cx.set_state(indoc! {"
15540        fn a() {
15541        //    «a();
15542        ///    b();
15543        //!    c();ˇ»
15544        }
15545    "});
15546
15547    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15548
15549    cx.assert_editor_state(indoc! {"
15550        fn a() {
15551            «a();
15552            b();
15553            c();ˇ»
15554        }
15555    "});
15556}
15557
15558#[gpui::test]
15559async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15560    init_test(cx, |_| {});
15561
15562    let language = Arc::new(Language::new(
15563        LanguageConfig {
15564            line_comments: vec!["// ".into()],
15565            ..Default::default()
15566        },
15567        Some(tree_sitter_rust::LANGUAGE.into()),
15568    ));
15569
15570    let mut cx = EditorTestContext::new(cx).await;
15571
15572    cx.language_registry().add(language.clone());
15573    cx.update_buffer(|buffer, cx| {
15574        buffer.set_language(Some(language), cx);
15575    });
15576
15577    let toggle_comments = &ToggleComments {
15578        advance_downwards: true,
15579        ignore_indent: false,
15580    };
15581
15582    // Single cursor on one line -> advance
15583    // Cursor moves horizontally 3 characters as well on non-blank line
15584    cx.set_state(indoc!(
15585        "fn a() {
15586             ˇdog();
15587             cat();
15588        }"
15589    ));
15590    cx.update_editor(|editor, window, cx| {
15591        editor.toggle_comments(toggle_comments, window, cx);
15592    });
15593    cx.assert_editor_state(indoc!(
15594        "fn a() {
15595             // dog();
15596             catˇ();
15597        }"
15598    ));
15599
15600    // Single selection on one line -> don't advance
15601    cx.set_state(indoc!(
15602        "fn a() {
15603             «dog()ˇ»;
15604             cat();
15605        }"
15606    ));
15607    cx.update_editor(|editor, window, cx| {
15608        editor.toggle_comments(toggle_comments, window, cx);
15609    });
15610    cx.assert_editor_state(indoc!(
15611        "fn a() {
15612             // «dog()ˇ»;
15613             cat();
15614        }"
15615    ));
15616
15617    // Multiple cursors on one line -> advance
15618    cx.set_state(indoc!(
15619        "fn a() {
15620             ˇdˇog();
15621             cat();
15622        }"
15623    ));
15624    cx.update_editor(|editor, window, cx| {
15625        editor.toggle_comments(toggle_comments, window, cx);
15626    });
15627    cx.assert_editor_state(indoc!(
15628        "fn a() {
15629             // dog();
15630             catˇ(ˇ);
15631        }"
15632    ));
15633
15634    // Multiple cursors on one line, with selection -> don't advance
15635    cx.set_state(indoc!(
15636        "fn a() {
15637             ˇdˇog«()ˇ»;
15638             cat();
15639        }"
15640    ));
15641    cx.update_editor(|editor, window, cx| {
15642        editor.toggle_comments(toggle_comments, window, cx);
15643    });
15644    cx.assert_editor_state(indoc!(
15645        "fn a() {
15646             // ˇdˇog«()ˇ»;
15647             cat();
15648        }"
15649    ));
15650
15651    // Single cursor on one line -> advance
15652    // Cursor moves to column 0 on blank line
15653    cx.set_state(indoc!(
15654        "fn a() {
15655             ˇdog();
15656
15657             cat();
15658        }"
15659    ));
15660    cx.update_editor(|editor, window, cx| {
15661        editor.toggle_comments(toggle_comments, window, cx);
15662    });
15663    cx.assert_editor_state(indoc!(
15664        "fn a() {
15665             // dog();
15666        ˇ
15667             cat();
15668        }"
15669    ));
15670
15671    // Single cursor on one line -> advance
15672    // Cursor starts and ends at column 0
15673    cx.set_state(indoc!(
15674        "fn a() {
15675         ˇ    dog();
15676             cat();
15677        }"
15678    ));
15679    cx.update_editor(|editor, window, cx| {
15680        editor.toggle_comments(toggle_comments, window, cx);
15681    });
15682    cx.assert_editor_state(indoc!(
15683        "fn a() {
15684             // dog();
15685         ˇ    cat();
15686        }"
15687    ));
15688}
15689
15690#[gpui::test]
15691async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15692    init_test(cx, |_| {});
15693
15694    let mut cx = EditorTestContext::new(cx).await;
15695
15696    let html_language = Arc::new(
15697        Language::new(
15698            LanguageConfig {
15699                name: "HTML".into(),
15700                block_comment: Some(BlockCommentConfig {
15701                    start: "<!-- ".into(),
15702                    prefix: "".into(),
15703                    end: " -->".into(),
15704                    tab_size: 0,
15705                }),
15706                ..Default::default()
15707            },
15708            Some(tree_sitter_html::LANGUAGE.into()),
15709        )
15710        .with_injection_query(
15711            r#"
15712            (script_element
15713                (raw_text) @injection.content
15714                (#set! injection.language "javascript"))
15715            "#,
15716        )
15717        .unwrap(),
15718    );
15719
15720    let javascript_language = Arc::new(Language::new(
15721        LanguageConfig {
15722            name: "JavaScript".into(),
15723            line_comments: vec!["// ".into()],
15724            ..Default::default()
15725        },
15726        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15727    ));
15728
15729    cx.language_registry().add(html_language.clone());
15730    cx.language_registry().add(javascript_language);
15731    cx.update_buffer(|buffer, cx| {
15732        buffer.set_language(Some(html_language), cx);
15733    });
15734
15735    // Toggle comments for empty selections
15736    cx.set_state(
15737        &r#"
15738            <p>A</p>ˇ
15739            <p>B</p>ˇ
15740            <p>C</p>ˇ
15741        "#
15742        .unindent(),
15743    );
15744    cx.update_editor(|editor, window, cx| {
15745        editor.toggle_comments(&ToggleComments::default(), window, cx)
15746    });
15747    cx.assert_editor_state(
15748        &r#"
15749            <!-- <p>A</p>ˇ -->
15750            <!-- <p>B</p>ˇ -->
15751            <!-- <p>C</p>ˇ -->
15752        "#
15753        .unindent(),
15754    );
15755    cx.update_editor(|editor, window, cx| {
15756        editor.toggle_comments(&ToggleComments::default(), window, cx)
15757    });
15758    cx.assert_editor_state(
15759        &r#"
15760            <p>A</p>ˇ
15761            <p>B</p>ˇ
15762            <p>C</p>ˇ
15763        "#
15764        .unindent(),
15765    );
15766
15767    // Toggle comments for mixture of empty and non-empty selections, where
15768    // multiple selections occupy a given line.
15769    cx.set_state(
15770        &r#"
15771            <p>A«</p>
15772            <p>ˇ»B</p>ˇ
15773            <p>C«</p>
15774            <p>ˇ»D</p>ˇ
15775        "#
15776        .unindent(),
15777    );
15778
15779    cx.update_editor(|editor, window, cx| {
15780        editor.toggle_comments(&ToggleComments::default(), window, cx)
15781    });
15782    cx.assert_editor_state(
15783        &r#"
15784            <!-- <p>A«</p>
15785            <p>ˇ»B</p>ˇ -->
15786            <!-- <p>C«</p>
15787            <p>ˇ»D</p>ˇ -->
15788        "#
15789        .unindent(),
15790    );
15791    cx.update_editor(|editor, window, cx| {
15792        editor.toggle_comments(&ToggleComments::default(), window, cx)
15793    });
15794    cx.assert_editor_state(
15795        &r#"
15796            <p>A«</p>
15797            <p>ˇ»B</p>ˇ
15798            <p>C«</p>
15799            <p>ˇ»D</p>ˇ
15800        "#
15801        .unindent(),
15802    );
15803
15804    // Toggle comments when different languages are active for different
15805    // selections.
15806    cx.set_state(
15807        &r#"
15808            ˇ<script>
15809                ˇvar x = new Y();
15810            ˇ</script>
15811        "#
15812        .unindent(),
15813    );
15814    cx.executor().run_until_parked();
15815    cx.update_editor(|editor, window, cx| {
15816        editor.toggle_comments(&ToggleComments::default(), window, cx)
15817    });
15818    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15819    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15820    cx.assert_editor_state(
15821        &r#"
15822            <!-- ˇ<script> -->
15823                // ˇvar x = new Y();
15824            <!-- ˇ</script> -->
15825        "#
15826        .unindent(),
15827    );
15828}
15829
15830#[gpui::test]
15831fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15832    init_test(cx, |_| {});
15833
15834    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15835    let multibuffer = cx.new(|cx| {
15836        let mut multibuffer = MultiBuffer::new(ReadWrite);
15837        multibuffer.push_excerpts(
15838            buffer.clone(),
15839            [
15840                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15841                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15842            ],
15843            cx,
15844        );
15845        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15846        multibuffer
15847    });
15848
15849    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15850    editor.update_in(cx, |editor, window, cx| {
15851        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15852        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15853            s.select_ranges([
15854                Point::new(0, 0)..Point::new(0, 0),
15855                Point::new(1, 0)..Point::new(1, 0),
15856            ])
15857        });
15858
15859        editor.handle_input("X", window, cx);
15860        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15861        assert_eq!(
15862            editor.selections.ranges(cx),
15863            [
15864                Point::new(0, 1)..Point::new(0, 1),
15865                Point::new(1, 1)..Point::new(1, 1),
15866            ]
15867        );
15868
15869        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15870        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15871            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15872        });
15873        editor.backspace(&Default::default(), window, cx);
15874        assert_eq!(editor.text(cx), "Xa\nbbb");
15875        assert_eq!(
15876            editor.selections.ranges(cx),
15877            [Point::new(1, 0)..Point::new(1, 0)]
15878        );
15879
15880        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15881            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15882        });
15883        editor.backspace(&Default::default(), window, cx);
15884        assert_eq!(editor.text(cx), "X\nbb");
15885        assert_eq!(
15886            editor.selections.ranges(cx),
15887            [Point::new(0, 1)..Point::new(0, 1)]
15888        );
15889    });
15890}
15891
15892#[gpui::test]
15893fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15894    init_test(cx, |_| {});
15895
15896    let markers = vec![('[', ']').into(), ('(', ')').into()];
15897    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15898        indoc! {"
15899            [aaaa
15900            (bbbb]
15901            cccc)",
15902        },
15903        markers.clone(),
15904    );
15905    let excerpt_ranges = markers.into_iter().map(|marker| {
15906        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15907        ExcerptRange::new(context)
15908    });
15909    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15910    let multibuffer = cx.new(|cx| {
15911        let mut multibuffer = MultiBuffer::new(ReadWrite);
15912        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15913        multibuffer
15914    });
15915
15916    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15917    editor.update_in(cx, |editor, window, cx| {
15918        let (expected_text, selection_ranges) = marked_text_ranges(
15919            indoc! {"
15920                aaaa
15921                bˇbbb
15922                bˇbbˇb
15923                cccc"
15924            },
15925            true,
15926        );
15927        assert_eq!(editor.text(cx), expected_text);
15928        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15929            s.select_ranges(selection_ranges)
15930        });
15931
15932        editor.handle_input("X", window, cx);
15933
15934        let (expected_text, expected_selections) = marked_text_ranges(
15935            indoc! {"
15936                aaaa
15937                bXˇbbXb
15938                bXˇbbXˇb
15939                cccc"
15940            },
15941            false,
15942        );
15943        assert_eq!(editor.text(cx), expected_text);
15944        assert_eq!(editor.selections.ranges(cx), expected_selections);
15945
15946        editor.newline(&Newline, window, cx);
15947        let (expected_text, expected_selections) = marked_text_ranges(
15948            indoc! {"
15949                aaaa
15950                bX
15951                ˇbbX
15952                b
15953                bX
15954                ˇbbX
15955                ˇb
15956                cccc"
15957            },
15958            false,
15959        );
15960        assert_eq!(editor.text(cx), expected_text);
15961        assert_eq!(editor.selections.ranges(cx), expected_selections);
15962    });
15963}
15964
15965#[gpui::test]
15966fn test_refresh_selections(cx: &mut TestAppContext) {
15967    init_test(cx, |_| {});
15968
15969    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15970    let mut excerpt1_id = None;
15971    let multibuffer = cx.new(|cx| {
15972        let mut multibuffer = MultiBuffer::new(ReadWrite);
15973        excerpt1_id = multibuffer
15974            .push_excerpts(
15975                buffer.clone(),
15976                [
15977                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15978                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15979                ],
15980                cx,
15981            )
15982            .into_iter()
15983            .next();
15984        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15985        multibuffer
15986    });
15987
15988    let editor = cx.add_window(|window, cx| {
15989        let mut editor = build_editor(multibuffer.clone(), window, cx);
15990        let snapshot = editor.snapshot(window, cx);
15991        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15992            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15993        });
15994        editor.begin_selection(
15995            Point::new(2, 1).to_display_point(&snapshot),
15996            true,
15997            1,
15998            window,
15999            cx,
16000        );
16001        assert_eq!(
16002            editor.selections.ranges(cx),
16003            [
16004                Point::new(1, 3)..Point::new(1, 3),
16005                Point::new(2, 1)..Point::new(2, 1),
16006            ]
16007        );
16008        editor
16009    });
16010
16011    // Refreshing selections is a no-op when excerpts haven't changed.
16012    _ = editor.update(cx, |editor, window, cx| {
16013        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16014        assert_eq!(
16015            editor.selections.ranges(cx),
16016            [
16017                Point::new(1, 3)..Point::new(1, 3),
16018                Point::new(2, 1)..Point::new(2, 1),
16019            ]
16020        );
16021    });
16022
16023    multibuffer.update(cx, |multibuffer, cx| {
16024        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16025    });
16026    _ = editor.update(cx, |editor, window, cx| {
16027        // Removing an excerpt causes the first selection to become degenerate.
16028        assert_eq!(
16029            editor.selections.ranges(cx),
16030            [
16031                Point::new(0, 0)..Point::new(0, 0),
16032                Point::new(0, 1)..Point::new(0, 1)
16033            ]
16034        );
16035
16036        // Refreshing selections will relocate the first selection to the original buffer
16037        // location.
16038        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16039        assert_eq!(
16040            editor.selections.ranges(cx),
16041            [
16042                Point::new(0, 1)..Point::new(0, 1),
16043                Point::new(0, 3)..Point::new(0, 3)
16044            ]
16045        );
16046        assert!(editor.selections.pending_anchor().is_some());
16047    });
16048}
16049
16050#[gpui::test]
16051fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16052    init_test(cx, |_| {});
16053
16054    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16055    let mut excerpt1_id = None;
16056    let multibuffer = cx.new(|cx| {
16057        let mut multibuffer = MultiBuffer::new(ReadWrite);
16058        excerpt1_id = multibuffer
16059            .push_excerpts(
16060                buffer.clone(),
16061                [
16062                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16063                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16064                ],
16065                cx,
16066            )
16067            .into_iter()
16068            .next();
16069        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16070        multibuffer
16071    });
16072
16073    let editor = cx.add_window(|window, cx| {
16074        let mut editor = build_editor(multibuffer.clone(), window, cx);
16075        let snapshot = editor.snapshot(window, cx);
16076        editor.begin_selection(
16077            Point::new(1, 3).to_display_point(&snapshot),
16078            false,
16079            1,
16080            window,
16081            cx,
16082        );
16083        assert_eq!(
16084            editor.selections.ranges(cx),
16085            [Point::new(1, 3)..Point::new(1, 3)]
16086        );
16087        editor
16088    });
16089
16090    multibuffer.update(cx, |multibuffer, cx| {
16091        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16092    });
16093    _ = editor.update(cx, |editor, window, cx| {
16094        assert_eq!(
16095            editor.selections.ranges(cx),
16096            [Point::new(0, 0)..Point::new(0, 0)]
16097        );
16098
16099        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16100        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16101        assert_eq!(
16102            editor.selections.ranges(cx),
16103            [Point::new(0, 3)..Point::new(0, 3)]
16104        );
16105        assert!(editor.selections.pending_anchor().is_some());
16106    });
16107}
16108
16109#[gpui::test]
16110async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16111    init_test(cx, |_| {});
16112
16113    let language = Arc::new(
16114        Language::new(
16115            LanguageConfig {
16116                brackets: BracketPairConfig {
16117                    pairs: vec![
16118                        BracketPair {
16119                            start: "{".to_string(),
16120                            end: "}".to_string(),
16121                            close: true,
16122                            surround: true,
16123                            newline: true,
16124                        },
16125                        BracketPair {
16126                            start: "/* ".to_string(),
16127                            end: " */".to_string(),
16128                            close: true,
16129                            surround: true,
16130                            newline: true,
16131                        },
16132                    ],
16133                    ..Default::default()
16134                },
16135                ..Default::default()
16136            },
16137            Some(tree_sitter_rust::LANGUAGE.into()),
16138        )
16139        .with_indents_query("")
16140        .unwrap(),
16141    );
16142
16143    let text = concat!(
16144        "{   }\n",     //
16145        "  x\n",       //
16146        "  /*   */\n", //
16147        "x\n",         //
16148        "{{} }\n",     //
16149    );
16150
16151    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16152    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16153    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16154    editor
16155        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16156        .await;
16157
16158    editor.update_in(cx, |editor, window, cx| {
16159        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16160            s.select_display_ranges([
16161                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16162                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16163                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16164            ])
16165        });
16166        editor.newline(&Newline, window, cx);
16167
16168        assert_eq!(
16169            editor.buffer().read(cx).read(cx).text(),
16170            concat!(
16171                "{ \n",    // Suppress rustfmt
16172                "\n",      //
16173                "}\n",     //
16174                "  x\n",   //
16175                "  /* \n", //
16176                "  \n",    //
16177                "  */\n",  //
16178                "x\n",     //
16179                "{{} \n",  //
16180                "}\n",     //
16181            )
16182        );
16183    });
16184}
16185
16186#[gpui::test]
16187fn test_highlighted_ranges(cx: &mut TestAppContext) {
16188    init_test(cx, |_| {});
16189
16190    let editor = cx.add_window(|window, cx| {
16191        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16192        build_editor(buffer, window, cx)
16193    });
16194
16195    _ = editor.update(cx, |editor, window, cx| {
16196        struct Type1;
16197        struct Type2;
16198
16199        let buffer = editor.buffer.read(cx).snapshot(cx);
16200
16201        let anchor_range =
16202            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16203
16204        editor.highlight_background::<Type1>(
16205            &[
16206                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16207                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16208                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16209                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16210            ],
16211            |_| Hsla::red(),
16212            cx,
16213        );
16214        editor.highlight_background::<Type2>(
16215            &[
16216                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16217                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16218                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16219                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16220            ],
16221            |_| Hsla::green(),
16222            cx,
16223        );
16224
16225        let snapshot = editor.snapshot(window, cx);
16226        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16227            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16228            &snapshot,
16229            cx.theme(),
16230        );
16231        assert_eq!(
16232            highlighted_ranges,
16233            &[
16234                (
16235                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16236                    Hsla::green(),
16237                ),
16238                (
16239                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16240                    Hsla::red(),
16241                ),
16242                (
16243                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16244                    Hsla::green(),
16245                ),
16246                (
16247                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16248                    Hsla::red(),
16249                ),
16250            ]
16251        );
16252        assert_eq!(
16253            editor.sorted_background_highlights_in_range(
16254                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16255                &snapshot,
16256                cx.theme(),
16257            ),
16258            &[(
16259                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16260                Hsla::red(),
16261            )]
16262        );
16263    });
16264}
16265
16266#[gpui::test]
16267async fn test_following(cx: &mut TestAppContext) {
16268    init_test(cx, |_| {});
16269
16270    let fs = FakeFs::new(cx.executor());
16271    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16272
16273    let buffer = project.update(cx, |project, cx| {
16274        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16275        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16276    });
16277    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16278    let follower = cx.update(|cx| {
16279        cx.open_window(
16280            WindowOptions {
16281                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16282                    gpui::Point::new(px(0.), px(0.)),
16283                    gpui::Point::new(px(10.), px(80.)),
16284                ))),
16285                ..Default::default()
16286            },
16287            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16288        )
16289        .unwrap()
16290    });
16291
16292    let is_still_following = Rc::new(RefCell::new(true));
16293    let follower_edit_event_count = Rc::new(RefCell::new(0));
16294    let pending_update = Rc::new(RefCell::new(None));
16295    let leader_entity = leader.root(cx).unwrap();
16296    let follower_entity = follower.root(cx).unwrap();
16297    _ = follower.update(cx, {
16298        let update = pending_update.clone();
16299        let is_still_following = is_still_following.clone();
16300        let follower_edit_event_count = follower_edit_event_count.clone();
16301        |_, window, cx| {
16302            cx.subscribe_in(
16303                &leader_entity,
16304                window,
16305                move |_, leader, event, window, cx| {
16306                    leader.read(cx).add_event_to_update_proto(
16307                        event,
16308                        &mut update.borrow_mut(),
16309                        window,
16310                        cx,
16311                    );
16312                },
16313            )
16314            .detach();
16315
16316            cx.subscribe_in(
16317                &follower_entity,
16318                window,
16319                move |_, _, event: &EditorEvent, _window, _cx| {
16320                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16321                        *is_still_following.borrow_mut() = false;
16322                    }
16323
16324                    if let EditorEvent::BufferEdited = event {
16325                        *follower_edit_event_count.borrow_mut() += 1;
16326                    }
16327                },
16328            )
16329            .detach();
16330        }
16331    });
16332
16333    // Update the selections only
16334    _ = leader.update(cx, |leader, window, cx| {
16335        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16336            s.select_ranges([1..1])
16337        });
16338    });
16339    follower
16340        .update(cx, |follower, window, cx| {
16341            follower.apply_update_proto(
16342                &project,
16343                pending_update.borrow_mut().take().unwrap(),
16344                window,
16345                cx,
16346            )
16347        })
16348        .unwrap()
16349        .await
16350        .unwrap();
16351    _ = follower.update(cx, |follower, _, cx| {
16352        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
16353    });
16354    assert!(*is_still_following.borrow());
16355    assert_eq!(*follower_edit_event_count.borrow(), 0);
16356
16357    // Update the scroll position only
16358    _ = leader.update(cx, |leader, window, cx| {
16359        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16360    });
16361    follower
16362        .update(cx, |follower, window, cx| {
16363            follower.apply_update_proto(
16364                &project,
16365                pending_update.borrow_mut().take().unwrap(),
16366                window,
16367                cx,
16368            )
16369        })
16370        .unwrap()
16371        .await
16372        .unwrap();
16373    assert_eq!(
16374        follower
16375            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16376            .unwrap(),
16377        gpui::Point::new(1.5, 3.5)
16378    );
16379    assert!(*is_still_following.borrow());
16380    assert_eq!(*follower_edit_event_count.borrow(), 0);
16381
16382    // Update the selections and scroll position. The follower's scroll position is updated
16383    // via autoscroll, not via the leader's exact scroll position.
16384    _ = leader.update(cx, |leader, window, cx| {
16385        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16386            s.select_ranges([0..0])
16387        });
16388        leader.request_autoscroll(Autoscroll::newest(), cx);
16389        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16390    });
16391    follower
16392        .update(cx, |follower, window, cx| {
16393            follower.apply_update_proto(
16394                &project,
16395                pending_update.borrow_mut().take().unwrap(),
16396                window,
16397                cx,
16398            )
16399        })
16400        .unwrap()
16401        .await
16402        .unwrap();
16403    _ = follower.update(cx, |follower, _, cx| {
16404        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16405        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16406    });
16407    assert!(*is_still_following.borrow());
16408
16409    // Creating a pending selection that precedes another selection
16410    _ = leader.update(cx, |leader, window, cx| {
16411        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16412            s.select_ranges([1..1])
16413        });
16414        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16415    });
16416    follower
16417        .update(cx, |follower, window, cx| {
16418            follower.apply_update_proto(
16419                &project,
16420                pending_update.borrow_mut().take().unwrap(),
16421                window,
16422                cx,
16423            )
16424        })
16425        .unwrap()
16426        .await
16427        .unwrap();
16428    _ = follower.update(cx, |follower, _, cx| {
16429        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16430    });
16431    assert!(*is_still_following.borrow());
16432
16433    // Extend the pending selection so that it surrounds another selection
16434    _ = leader.update(cx, |leader, window, cx| {
16435        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16436    });
16437    follower
16438        .update(cx, |follower, window, cx| {
16439            follower.apply_update_proto(
16440                &project,
16441                pending_update.borrow_mut().take().unwrap(),
16442                window,
16443                cx,
16444            )
16445        })
16446        .unwrap()
16447        .await
16448        .unwrap();
16449    _ = follower.update(cx, |follower, _, cx| {
16450        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16451    });
16452
16453    // Scrolling locally breaks the follow
16454    _ = follower.update(cx, |follower, window, cx| {
16455        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16456        follower.set_scroll_anchor(
16457            ScrollAnchor {
16458                anchor: top_anchor,
16459                offset: gpui::Point::new(0.0, 0.5),
16460            },
16461            window,
16462            cx,
16463        );
16464    });
16465    assert!(!(*is_still_following.borrow()));
16466}
16467
16468#[gpui::test]
16469async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16470    init_test(cx, |_| {});
16471
16472    let fs = FakeFs::new(cx.executor());
16473    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16474    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16475    let pane = workspace
16476        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16477        .unwrap();
16478
16479    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16480
16481    let leader = pane.update_in(cx, |_, window, cx| {
16482        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16483        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16484    });
16485
16486    // Start following the editor when it has no excerpts.
16487    let mut state_message =
16488        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16489    let workspace_entity = workspace.root(cx).unwrap();
16490    let follower_1 = cx
16491        .update_window(*workspace.deref(), |_, window, cx| {
16492            Editor::from_state_proto(
16493                workspace_entity,
16494                ViewId {
16495                    creator: CollaboratorId::PeerId(PeerId::default()),
16496                    id: 0,
16497                },
16498                &mut state_message,
16499                window,
16500                cx,
16501            )
16502        })
16503        .unwrap()
16504        .unwrap()
16505        .await
16506        .unwrap();
16507
16508    let update_message = Rc::new(RefCell::new(None));
16509    follower_1.update_in(cx, {
16510        let update = update_message.clone();
16511        |_, window, cx| {
16512            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16513                leader.read(cx).add_event_to_update_proto(
16514                    event,
16515                    &mut update.borrow_mut(),
16516                    window,
16517                    cx,
16518                );
16519            })
16520            .detach();
16521        }
16522    });
16523
16524    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16525        (
16526            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16527            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16528        )
16529    });
16530
16531    // Insert some excerpts.
16532    leader.update(cx, |leader, cx| {
16533        leader.buffer.update(cx, |multibuffer, cx| {
16534            multibuffer.set_excerpts_for_path(
16535                PathKey::namespaced(1, rel_path("b.txt").into_arc()),
16536                buffer_1.clone(),
16537                vec![
16538                    Point::row_range(0..3),
16539                    Point::row_range(1..6),
16540                    Point::row_range(12..15),
16541                ],
16542                0,
16543                cx,
16544            );
16545            multibuffer.set_excerpts_for_path(
16546                PathKey::namespaced(1, rel_path("a.txt").into_arc()),
16547                buffer_2.clone(),
16548                vec![Point::row_range(0..6), Point::row_range(8..12)],
16549                0,
16550                cx,
16551            );
16552        });
16553    });
16554
16555    // Apply the update of adding the excerpts.
16556    follower_1
16557        .update_in(cx, |follower, window, cx| {
16558            follower.apply_update_proto(
16559                &project,
16560                update_message.borrow().clone().unwrap(),
16561                window,
16562                cx,
16563            )
16564        })
16565        .await
16566        .unwrap();
16567    assert_eq!(
16568        follower_1.update(cx, |editor, cx| editor.text(cx)),
16569        leader.update(cx, |editor, cx| editor.text(cx))
16570    );
16571    update_message.borrow_mut().take();
16572
16573    // Start following separately after it already has excerpts.
16574    let mut state_message =
16575        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16576    let workspace_entity = workspace.root(cx).unwrap();
16577    let follower_2 = cx
16578        .update_window(*workspace.deref(), |_, window, cx| {
16579            Editor::from_state_proto(
16580                workspace_entity,
16581                ViewId {
16582                    creator: CollaboratorId::PeerId(PeerId::default()),
16583                    id: 0,
16584                },
16585                &mut state_message,
16586                window,
16587                cx,
16588            )
16589        })
16590        .unwrap()
16591        .unwrap()
16592        .await
16593        .unwrap();
16594    assert_eq!(
16595        follower_2.update(cx, |editor, cx| editor.text(cx)),
16596        leader.update(cx, |editor, cx| editor.text(cx))
16597    );
16598
16599    // Remove some excerpts.
16600    leader.update(cx, |leader, cx| {
16601        leader.buffer.update(cx, |multibuffer, cx| {
16602            let excerpt_ids = multibuffer.excerpt_ids();
16603            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16604            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16605        });
16606    });
16607
16608    // Apply the update of removing the excerpts.
16609    follower_1
16610        .update_in(cx, |follower, window, cx| {
16611            follower.apply_update_proto(
16612                &project,
16613                update_message.borrow().clone().unwrap(),
16614                window,
16615                cx,
16616            )
16617        })
16618        .await
16619        .unwrap();
16620    follower_2
16621        .update_in(cx, |follower, window, cx| {
16622            follower.apply_update_proto(
16623                &project,
16624                update_message.borrow().clone().unwrap(),
16625                window,
16626                cx,
16627            )
16628        })
16629        .await
16630        .unwrap();
16631    update_message.borrow_mut().take();
16632    assert_eq!(
16633        follower_1.update(cx, |editor, cx| editor.text(cx)),
16634        leader.update(cx, |editor, cx| editor.text(cx))
16635    );
16636}
16637
16638#[gpui::test]
16639async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16640    init_test(cx, |_| {});
16641
16642    let mut cx = EditorTestContext::new(cx).await;
16643    let lsp_store =
16644        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16645
16646    cx.set_state(indoc! {"
16647        ˇfn func(abc def: i32) -> u32 {
16648        }
16649    "});
16650
16651    cx.update(|_, cx| {
16652        lsp_store.update(cx, |lsp_store, cx| {
16653            lsp_store
16654                .update_diagnostics(
16655                    LanguageServerId(0),
16656                    lsp::PublishDiagnosticsParams {
16657                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16658                        version: None,
16659                        diagnostics: vec![
16660                            lsp::Diagnostic {
16661                                range: lsp::Range::new(
16662                                    lsp::Position::new(0, 11),
16663                                    lsp::Position::new(0, 12),
16664                                ),
16665                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16666                                ..Default::default()
16667                            },
16668                            lsp::Diagnostic {
16669                                range: lsp::Range::new(
16670                                    lsp::Position::new(0, 12),
16671                                    lsp::Position::new(0, 15),
16672                                ),
16673                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16674                                ..Default::default()
16675                            },
16676                            lsp::Diagnostic {
16677                                range: lsp::Range::new(
16678                                    lsp::Position::new(0, 25),
16679                                    lsp::Position::new(0, 28),
16680                                ),
16681                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16682                                ..Default::default()
16683                            },
16684                        ],
16685                    },
16686                    None,
16687                    DiagnosticSourceKind::Pushed,
16688                    &[],
16689                    cx,
16690                )
16691                .unwrap()
16692        });
16693    });
16694
16695    executor.run_until_parked();
16696
16697    cx.update_editor(|editor, window, cx| {
16698        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16699    });
16700
16701    cx.assert_editor_state(indoc! {"
16702        fn func(abc def: i32) -> ˇu32 {
16703        }
16704    "});
16705
16706    cx.update_editor(|editor, window, cx| {
16707        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16708    });
16709
16710    cx.assert_editor_state(indoc! {"
16711        fn func(abc ˇdef: i32) -> u32 {
16712        }
16713    "});
16714
16715    cx.update_editor(|editor, window, cx| {
16716        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16717    });
16718
16719    cx.assert_editor_state(indoc! {"
16720        fn func(abcˇ def: i32) -> u32 {
16721        }
16722    "});
16723
16724    cx.update_editor(|editor, window, cx| {
16725        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16726    });
16727
16728    cx.assert_editor_state(indoc! {"
16729        fn func(abc def: i32) -> ˇu32 {
16730        }
16731    "});
16732}
16733
16734#[gpui::test]
16735async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16736    init_test(cx, |_| {});
16737
16738    let mut cx = EditorTestContext::new(cx).await;
16739
16740    let diff_base = r#"
16741        use some::mod;
16742
16743        const A: u32 = 42;
16744
16745        fn main() {
16746            println!("hello");
16747
16748            println!("world");
16749        }
16750        "#
16751    .unindent();
16752
16753    // Edits are modified, removed, modified, added
16754    cx.set_state(
16755        &r#"
16756        use some::modified;
16757
16758        ˇ
16759        fn main() {
16760            println!("hello there");
16761
16762            println!("around the");
16763            println!("world");
16764        }
16765        "#
16766        .unindent(),
16767    );
16768
16769    cx.set_head_text(&diff_base);
16770    executor.run_until_parked();
16771
16772    cx.update_editor(|editor, window, cx| {
16773        //Wrap around the bottom of the buffer
16774        for _ in 0..3 {
16775            editor.go_to_next_hunk(&GoToHunk, window, cx);
16776        }
16777    });
16778
16779    cx.assert_editor_state(
16780        &r#"
16781        ˇuse some::modified;
16782
16783
16784        fn main() {
16785            println!("hello there");
16786
16787            println!("around the");
16788            println!("world");
16789        }
16790        "#
16791        .unindent(),
16792    );
16793
16794    cx.update_editor(|editor, window, cx| {
16795        //Wrap around the top of the buffer
16796        for _ in 0..2 {
16797            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16798        }
16799    });
16800
16801    cx.assert_editor_state(
16802        &r#"
16803        use some::modified;
16804
16805
16806        fn main() {
16807        ˇ    println!("hello there");
16808
16809            println!("around the");
16810            println!("world");
16811        }
16812        "#
16813        .unindent(),
16814    );
16815
16816    cx.update_editor(|editor, window, cx| {
16817        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16818    });
16819
16820    cx.assert_editor_state(
16821        &r#"
16822        use some::modified;
16823
16824        ˇ
16825        fn main() {
16826            println!("hello there");
16827
16828            println!("around the");
16829            println!("world");
16830        }
16831        "#
16832        .unindent(),
16833    );
16834
16835    cx.update_editor(|editor, window, cx| {
16836        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16837    });
16838
16839    cx.assert_editor_state(
16840        &r#"
16841        ˇuse some::modified;
16842
16843
16844        fn main() {
16845            println!("hello there");
16846
16847            println!("around the");
16848            println!("world");
16849        }
16850        "#
16851        .unindent(),
16852    );
16853
16854    cx.update_editor(|editor, window, cx| {
16855        for _ in 0..2 {
16856            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16857        }
16858    });
16859
16860    cx.assert_editor_state(
16861        &r#"
16862        use some::modified;
16863
16864
16865        fn main() {
16866        ˇ    println!("hello there");
16867
16868            println!("around the");
16869            println!("world");
16870        }
16871        "#
16872        .unindent(),
16873    );
16874
16875    cx.update_editor(|editor, window, cx| {
16876        editor.fold(&Fold, window, cx);
16877    });
16878
16879    cx.update_editor(|editor, window, cx| {
16880        editor.go_to_next_hunk(&GoToHunk, window, cx);
16881    });
16882
16883    cx.assert_editor_state(
16884        &r#"
16885        ˇuse some::modified;
16886
16887
16888        fn main() {
16889            println!("hello there");
16890
16891            println!("around the");
16892            println!("world");
16893        }
16894        "#
16895        .unindent(),
16896    );
16897}
16898
16899#[test]
16900fn test_split_words() {
16901    fn split(text: &str) -> Vec<&str> {
16902        split_words(text).collect()
16903    }
16904
16905    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16906    assert_eq!(split("hello_world"), &["hello_", "world"]);
16907    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16908    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16909    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16910    assert_eq!(split("helloworld"), &["helloworld"]);
16911
16912    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16913}
16914
16915#[gpui::test]
16916async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16917    init_test(cx, |_| {});
16918
16919    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16920    let mut assert = |before, after| {
16921        let _state_context = cx.set_state(before);
16922        cx.run_until_parked();
16923        cx.update_editor(|editor, window, cx| {
16924            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16925        });
16926        cx.run_until_parked();
16927        cx.assert_editor_state(after);
16928    };
16929
16930    // Outside bracket jumps to outside of matching bracket
16931    assert("console.logˇ(var);", "console.log(var)ˇ;");
16932    assert("console.log(var)ˇ;", "console.logˇ(var);");
16933
16934    // Inside bracket jumps to inside of matching bracket
16935    assert("console.log(ˇvar);", "console.log(varˇ);");
16936    assert("console.log(varˇ);", "console.log(ˇvar);");
16937
16938    // When outside a bracket and inside, favor jumping to the inside bracket
16939    assert(
16940        "console.log('foo', [1, 2, 3]ˇ);",
16941        "console.log(ˇ'foo', [1, 2, 3]);",
16942    );
16943    assert(
16944        "console.log(ˇ'foo', [1, 2, 3]);",
16945        "console.log('foo', [1, 2, 3]ˇ);",
16946    );
16947
16948    // Bias forward if two options are equally likely
16949    assert(
16950        "let result = curried_fun()ˇ();",
16951        "let result = curried_fun()()ˇ;",
16952    );
16953
16954    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16955    assert(
16956        indoc! {"
16957            function test() {
16958                console.log('test')ˇ
16959            }"},
16960        indoc! {"
16961            function test() {
16962                console.logˇ('test')
16963            }"},
16964    );
16965}
16966
16967#[gpui::test]
16968async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16969    init_test(cx, |_| {});
16970
16971    let fs = FakeFs::new(cx.executor());
16972    fs.insert_tree(
16973        path!("/a"),
16974        json!({
16975            "main.rs": "fn main() { let a = 5; }",
16976            "other.rs": "// Test file",
16977        }),
16978    )
16979    .await;
16980    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16981
16982    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16983    language_registry.add(Arc::new(Language::new(
16984        LanguageConfig {
16985            name: "Rust".into(),
16986            matcher: LanguageMatcher {
16987                path_suffixes: vec!["rs".to_string()],
16988                ..Default::default()
16989            },
16990            brackets: BracketPairConfig {
16991                pairs: vec![BracketPair {
16992                    start: "{".to_string(),
16993                    end: "}".to_string(),
16994                    close: true,
16995                    surround: true,
16996                    newline: true,
16997                }],
16998                disabled_scopes_by_bracket_ix: Vec::new(),
16999            },
17000            ..Default::default()
17001        },
17002        Some(tree_sitter_rust::LANGUAGE.into()),
17003    )));
17004    let mut fake_servers = language_registry.register_fake_lsp(
17005        "Rust",
17006        FakeLspAdapter {
17007            capabilities: lsp::ServerCapabilities {
17008                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17009                    first_trigger_character: "{".to_string(),
17010                    more_trigger_character: None,
17011                }),
17012                ..Default::default()
17013            },
17014            ..Default::default()
17015        },
17016    );
17017
17018    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17019
17020    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17021
17022    let worktree_id = workspace
17023        .update(cx, |workspace, _, cx| {
17024            workspace.project().update(cx, |project, cx| {
17025                project.worktrees(cx).next().unwrap().read(cx).id()
17026            })
17027        })
17028        .unwrap();
17029
17030    let buffer = project
17031        .update(cx, |project, cx| {
17032            project.open_local_buffer(path!("/a/main.rs"), cx)
17033        })
17034        .await
17035        .unwrap();
17036    let editor_handle = workspace
17037        .update(cx, |workspace, window, cx| {
17038            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17039        })
17040        .unwrap()
17041        .await
17042        .unwrap()
17043        .downcast::<Editor>()
17044        .unwrap();
17045
17046    cx.executor().start_waiting();
17047    let fake_server = fake_servers.next().await.unwrap();
17048
17049    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17050        |params, _| async move {
17051            assert_eq!(
17052                params.text_document_position.text_document.uri,
17053                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17054            );
17055            assert_eq!(
17056                params.text_document_position.position,
17057                lsp::Position::new(0, 21),
17058            );
17059
17060            Ok(Some(vec![lsp::TextEdit {
17061                new_text: "]".to_string(),
17062                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17063            }]))
17064        },
17065    );
17066
17067    editor_handle.update_in(cx, |editor, window, cx| {
17068        window.focus(&editor.focus_handle(cx));
17069        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17070            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17071        });
17072        editor.handle_input("{", window, cx);
17073    });
17074
17075    cx.executor().run_until_parked();
17076
17077    buffer.update(cx, |buffer, _| {
17078        assert_eq!(
17079            buffer.text(),
17080            "fn main() { let a = {5}; }",
17081            "No extra braces from on type formatting should appear in the buffer"
17082        )
17083    });
17084}
17085
17086#[gpui::test(iterations = 20, seeds(31))]
17087async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17088    init_test(cx, |_| {});
17089
17090    let mut cx = EditorLspTestContext::new_rust(
17091        lsp::ServerCapabilities {
17092            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17093                first_trigger_character: ".".to_string(),
17094                more_trigger_character: None,
17095            }),
17096            ..Default::default()
17097        },
17098        cx,
17099    )
17100    .await;
17101
17102    cx.update_buffer(|buffer, _| {
17103        // This causes autoindent to be async.
17104        buffer.set_sync_parse_timeout(Duration::ZERO)
17105    });
17106
17107    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17108    cx.simulate_keystroke("\n");
17109    cx.run_until_parked();
17110
17111    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17112    let mut request =
17113        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17114            let buffer_cloned = buffer_cloned.clone();
17115            async move {
17116                buffer_cloned.update(&mut cx, |buffer, _| {
17117                    assert_eq!(
17118                        buffer.text(),
17119                        "fn c() {\n    d()\n        .\n}\n",
17120                        "OnTypeFormatting should triggered after autoindent applied"
17121                    )
17122                })?;
17123
17124                Ok(Some(vec![]))
17125            }
17126        });
17127
17128    cx.simulate_keystroke(".");
17129    cx.run_until_parked();
17130
17131    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17132    assert!(request.next().await.is_some());
17133    request.close();
17134    assert!(request.next().await.is_none());
17135}
17136
17137#[gpui::test]
17138async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17139    init_test(cx, |_| {});
17140
17141    let fs = FakeFs::new(cx.executor());
17142    fs.insert_tree(
17143        path!("/a"),
17144        json!({
17145            "main.rs": "fn main() { let a = 5; }",
17146            "other.rs": "// Test file",
17147        }),
17148    )
17149    .await;
17150
17151    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17152
17153    let server_restarts = Arc::new(AtomicUsize::new(0));
17154    let closure_restarts = Arc::clone(&server_restarts);
17155    let language_server_name = "test language server";
17156    let language_name: LanguageName = "Rust".into();
17157
17158    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17159    language_registry.add(Arc::new(Language::new(
17160        LanguageConfig {
17161            name: language_name.clone(),
17162            matcher: LanguageMatcher {
17163                path_suffixes: vec!["rs".to_string()],
17164                ..Default::default()
17165            },
17166            ..Default::default()
17167        },
17168        Some(tree_sitter_rust::LANGUAGE.into()),
17169    )));
17170    let mut fake_servers = language_registry.register_fake_lsp(
17171        "Rust",
17172        FakeLspAdapter {
17173            name: language_server_name,
17174            initialization_options: Some(json!({
17175                "testOptionValue": true
17176            })),
17177            initializer: Some(Box::new(move |fake_server| {
17178                let task_restarts = Arc::clone(&closure_restarts);
17179                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17180                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17181                    futures::future::ready(Ok(()))
17182                });
17183            })),
17184            ..Default::default()
17185        },
17186    );
17187
17188    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17189    let _buffer = project
17190        .update(cx, |project, cx| {
17191            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17192        })
17193        .await
17194        .unwrap();
17195    let _fake_server = fake_servers.next().await.unwrap();
17196    update_test_language_settings(cx, |language_settings| {
17197        language_settings.languages.0.insert(
17198            language_name.clone().0,
17199            LanguageSettingsContent {
17200                tab_size: NonZeroU32::new(8),
17201                ..Default::default()
17202            },
17203        );
17204    });
17205    cx.executor().run_until_parked();
17206    assert_eq!(
17207        server_restarts.load(atomic::Ordering::Acquire),
17208        0,
17209        "Should not restart LSP server on an unrelated change"
17210    );
17211
17212    update_test_project_settings(cx, |project_settings| {
17213        project_settings.lsp.insert(
17214            "Some other server name".into(),
17215            LspSettings {
17216                binary: None,
17217                settings: None,
17218                initialization_options: Some(json!({
17219                    "some other init value": false
17220                })),
17221                enable_lsp_tasks: false,
17222                fetch: None,
17223            },
17224        );
17225    });
17226    cx.executor().run_until_parked();
17227    assert_eq!(
17228        server_restarts.load(atomic::Ordering::Acquire),
17229        0,
17230        "Should not restart LSP server on an unrelated LSP settings change"
17231    );
17232
17233    update_test_project_settings(cx, |project_settings| {
17234        project_settings.lsp.insert(
17235            language_server_name.into(),
17236            LspSettings {
17237                binary: None,
17238                settings: None,
17239                initialization_options: Some(json!({
17240                    "anotherInitValue": false
17241                })),
17242                enable_lsp_tasks: false,
17243                fetch: None,
17244            },
17245        );
17246    });
17247    cx.executor().run_until_parked();
17248    assert_eq!(
17249        server_restarts.load(atomic::Ordering::Acquire),
17250        1,
17251        "Should restart LSP server on a related LSP settings change"
17252    );
17253
17254    update_test_project_settings(cx, |project_settings| {
17255        project_settings.lsp.insert(
17256            language_server_name.into(),
17257            LspSettings {
17258                binary: None,
17259                settings: None,
17260                initialization_options: Some(json!({
17261                    "anotherInitValue": false
17262                })),
17263                enable_lsp_tasks: false,
17264                fetch: None,
17265            },
17266        );
17267    });
17268    cx.executor().run_until_parked();
17269    assert_eq!(
17270        server_restarts.load(atomic::Ordering::Acquire),
17271        1,
17272        "Should not restart LSP server on a related LSP settings change that is the same"
17273    );
17274
17275    update_test_project_settings(cx, |project_settings| {
17276        project_settings.lsp.insert(
17277            language_server_name.into(),
17278            LspSettings {
17279                binary: None,
17280                settings: None,
17281                initialization_options: None,
17282                enable_lsp_tasks: false,
17283                fetch: None,
17284            },
17285        );
17286    });
17287    cx.executor().run_until_parked();
17288    assert_eq!(
17289        server_restarts.load(atomic::Ordering::Acquire),
17290        2,
17291        "Should restart LSP server on another related LSP settings change"
17292    );
17293}
17294
17295#[gpui::test]
17296async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17297    init_test(cx, |_| {});
17298
17299    let mut cx = EditorLspTestContext::new_rust(
17300        lsp::ServerCapabilities {
17301            completion_provider: Some(lsp::CompletionOptions {
17302                trigger_characters: Some(vec![".".to_string()]),
17303                resolve_provider: Some(true),
17304                ..Default::default()
17305            }),
17306            ..Default::default()
17307        },
17308        cx,
17309    )
17310    .await;
17311
17312    cx.set_state("fn main() { let a = 2ˇ; }");
17313    cx.simulate_keystroke(".");
17314    let completion_item = lsp::CompletionItem {
17315        label: "some".into(),
17316        kind: Some(lsp::CompletionItemKind::SNIPPET),
17317        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17318        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17319            kind: lsp::MarkupKind::Markdown,
17320            value: "```rust\nSome(2)\n```".to_string(),
17321        })),
17322        deprecated: Some(false),
17323        sort_text: Some("fffffff2".to_string()),
17324        filter_text: Some("some".to_string()),
17325        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17326        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17327            range: lsp::Range {
17328                start: lsp::Position {
17329                    line: 0,
17330                    character: 22,
17331                },
17332                end: lsp::Position {
17333                    line: 0,
17334                    character: 22,
17335                },
17336            },
17337            new_text: "Some(2)".to_string(),
17338        })),
17339        additional_text_edits: Some(vec![lsp::TextEdit {
17340            range: lsp::Range {
17341                start: lsp::Position {
17342                    line: 0,
17343                    character: 20,
17344                },
17345                end: lsp::Position {
17346                    line: 0,
17347                    character: 22,
17348                },
17349            },
17350            new_text: "".to_string(),
17351        }]),
17352        ..Default::default()
17353    };
17354
17355    let closure_completion_item = completion_item.clone();
17356    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17357        let task_completion_item = closure_completion_item.clone();
17358        async move {
17359            Ok(Some(lsp::CompletionResponse::Array(vec![
17360                task_completion_item,
17361            ])))
17362        }
17363    });
17364
17365    request.next().await;
17366
17367    cx.condition(|editor, _| editor.context_menu_visible())
17368        .await;
17369    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17370        editor
17371            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17372            .unwrap()
17373    });
17374    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17375
17376    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17377        let task_completion_item = completion_item.clone();
17378        async move { Ok(task_completion_item) }
17379    })
17380    .next()
17381    .await
17382    .unwrap();
17383    apply_additional_edits.await.unwrap();
17384    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17385}
17386
17387#[gpui::test]
17388async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17389    init_test(cx, |_| {});
17390
17391    let mut cx = EditorLspTestContext::new_rust(
17392        lsp::ServerCapabilities {
17393            completion_provider: Some(lsp::CompletionOptions {
17394                trigger_characters: Some(vec![".".to_string()]),
17395                resolve_provider: Some(true),
17396                ..Default::default()
17397            }),
17398            ..Default::default()
17399        },
17400        cx,
17401    )
17402    .await;
17403
17404    cx.set_state("fn main() { let a = 2ˇ; }");
17405    cx.simulate_keystroke(".");
17406
17407    let item1 = lsp::CompletionItem {
17408        label: "method id()".to_string(),
17409        filter_text: Some("id".to_string()),
17410        detail: None,
17411        documentation: None,
17412        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17413            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17414            new_text: ".id".to_string(),
17415        })),
17416        ..lsp::CompletionItem::default()
17417    };
17418
17419    let item2 = lsp::CompletionItem {
17420        label: "other".to_string(),
17421        filter_text: Some("other".to_string()),
17422        detail: None,
17423        documentation: None,
17424        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17425            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17426            new_text: ".other".to_string(),
17427        })),
17428        ..lsp::CompletionItem::default()
17429    };
17430
17431    let item1 = item1.clone();
17432    cx.set_request_handler::<lsp::request::Completion, _, _>({
17433        let item1 = item1.clone();
17434        move |_, _, _| {
17435            let item1 = item1.clone();
17436            let item2 = item2.clone();
17437            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17438        }
17439    })
17440    .next()
17441    .await;
17442
17443    cx.condition(|editor, _| editor.context_menu_visible())
17444        .await;
17445    cx.update_editor(|editor, _, _| {
17446        let context_menu = editor.context_menu.borrow_mut();
17447        let context_menu = context_menu
17448            .as_ref()
17449            .expect("Should have the context menu deployed");
17450        match context_menu {
17451            CodeContextMenu::Completions(completions_menu) => {
17452                let completions = completions_menu.completions.borrow_mut();
17453                assert_eq!(
17454                    completions
17455                        .iter()
17456                        .map(|completion| &completion.label.text)
17457                        .collect::<Vec<_>>(),
17458                    vec!["method id()", "other"]
17459                )
17460            }
17461            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17462        }
17463    });
17464
17465    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17466        let item1 = item1.clone();
17467        move |_, item_to_resolve, _| {
17468            let item1 = item1.clone();
17469            async move {
17470                if item1 == item_to_resolve {
17471                    Ok(lsp::CompletionItem {
17472                        label: "method id()".to_string(),
17473                        filter_text: Some("id".to_string()),
17474                        detail: Some("Now resolved!".to_string()),
17475                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17476                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17477                            range: lsp::Range::new(
17478                                lsp::Position::new(0, 22),
17479                                lsp::Position::new(0, 22),
17480                            ),
17481                            new_text: ".id".to_string(),
17482                        })),
17483                        ..lsp::CompletionItem::default()
17484                    })
17485                } else {
17486                    Ok(item_to_resolve)
17487                }
17488            }
17489        }
17490    })
17491    .next()
17492    .await
17493    .unwrap();
17494    cx.run_until_parked();
17495
17496    cx.update_editor(|editor, window, cx| {
17497        editor.context_menu_next(&Default::default(), window, cx);
17498    });
17499
17500    cx.update_editor(|editor, _, _| {
17501        let context_menu = editor.context_menu.borrow_mut();
17502        let context_menu = context_menu
17503            .as_ref()
17504            .expect("Should have the context menu deployed");
17505        match context_menu {
17506            CodeContextMenu::Completions(completions_menu) => {
17507                let completions = completions_menu.completions.borrow_mut();
17508                assert_eq!(
17509                    completions
17510                        .iter()
17511                        .map(|completion| &completion.label.text)
17512                        .collect::<Vec<_>>(),
17513                    vec!["method id() Now resolved!", "other"],
17514                    "Should update first completion label, but not second as the filter text did not match."
17515                );
17516            }
17517            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17518        }
17519    });
17520}
17521
17522#[gpui::test]
17523async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17524    init_test(cx, |_| {});
17525    let mut cx = EditorLspTestContext::new_rust(
17526        lsp::ServerCapabilities {
17527            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17528            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17529            completion_provider: Some(lsp::CompletionOptions {
17530                resolve_provider: Some(true),
17531                ..Default::default()
17532            }),
17533            ..Default::default()
17534        },
17535        cx,
17536    )
17537    .await;
17538    cx.set_state(indoc! {"
17539        struct TestStruct {
17540            field: i32
17541        }
17542
17543        fn mainˇ() {
17544            let unused_var = 42;
17545            let test_struct = TestStruct { field: 42 };
17546        }
17547    "});
17548    let symbol_range = cx.lsp_range(indoc! {"
17549        struct TestStruct {
17550            field: i32
17551        }
17552
17553        «fn main»() {
17554            let unused_var = 42;
17555            let test_struct = TestStruct { field: 42 };
17556        }
17557    "});
17558    let mut hover_requests =
17559        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17560            Ok(Some(lsp::Hover {
17561                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17562                    kind: lsp::MarkupKind::Markdown,
17563                    value: "Function documentation".to_string(),
17564                }),
17565                range: Some(symbol_range),
17566            }))
17567        });
17568
17569    // Case 1: Test that code action menu hide hover popover
17570    cx.dispatch_action(Hover);
17571    hover_requests.next().await;
17572    cx.condition(|editor, _| editor.hover_state.visible()).await;
17573    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17574        move |_, _, _| async move {
17575            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17576                lsp::CodeAction {
17577                    title: "Remove unused variable".to_string(),
17578                    kind: Some(CodeActionKind::QUICKFIX),
17579                    edit: Some(lsp::WorkspaceEdit {
17580                        changes: Some(
17581                            [(
17582                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17583                                vec![lsp::TextEdit {
17584                                    range: lsp::Range::new(
17585                                        lsp::Position::new(5, 4),
17586                                        lsp::Position::new(5, 27),
17587                                    ),
17588                                    new_text: "".to_string(),
17589                                }],
17590                            )]
17591                            .into_iter()
17592                            .collect(),
17593                        ),
17594                        ..Default::default()
17595                    }),
17596                    ..Default::default()
17597                },
17598            )]))
17599        },
17600    );
17601    cx.update_editor(|editor, window, cx| {
17602        editor.toggle_code_actions(
17603            &ToggleCodeActions {
17604                deployed_from: None,
17605                quick_launch: false,
17606            },
17607            window,
17608            cx,
17609        );
17610    });
17611    code_action_requests.next().await;
17612    cx.run_until_parked();
17613    cx.condition(|editor, _| editor.context_menu_visible())
17614        .await;
17615    cx.update_editor(|editor, _, _| {
17616        assert!(
17617            !editor.hover_state.visible(),
17618            "Hover popover should be hidden when code action menu is shown"
17619        );
17620        // Hide code actions
17621        editor.context_menu.take();
17622    });
17623
17624    // Case 2: Test that code completions hide hover popover
17625    cx.dispatch_action(Hover);
17626    hover_requests.next().await;
17627    cx.condition(|editor, _| editor.hover_state.visible()).await;
17628    let counter = Arc::new(AtomicUsize::new(0));
17629    let mut completion_requests =
17630        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17631            let counter = counter.clone();
17632            async move {
17633                counter.fetch_add(1, atomic::Ordering::Release);
17634                Ok(Some(lsp::CompletionResponse::Array(vec![
17635                    lsp::CompletionItem {
17636                        label: "main".into(),
17637                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17638                        detail: Some("() -> ()".to_string()),
17639                        ..Default::default()
17640                    },
17641                    lsp::CompletionItem {
17642                        label: "TestStruct".into(),
17643                        kind: Some(lsp::CompletionItemKind::STRUCT),
17644                        detail: Some("struct TestStruct".to_string()),
17645                        ..Default::default()
17646                    },
17647                ])))
17648            }
17649        });
17650    cx.update_editor(|editor, window, cx| {
17651        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17652    });
17653    completion_requests.next().await;
17654    cx.condition(|editor, _| editor.context_menu_visible())
17655        .await;
17656    cx.update_editor(|editor, _, _| {
17657        assert!(
17658            !editor.hover_state.visible(),
17659            "Hover popover should be hidden when completion menu is shown"
17660        );
17661    });
17662}
17663
17664#[gpui::test]
17665async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17666    init_test(cx, |_| {});
17667
17668    let mut cx = EditorLspTestContext::new_rust(
17669        lsp::ServerCapabilities {
17670            completion_provider: Some(lsp::CompletionOptions {
17671                trigger_characters: Some(vec![".".to_string()]),
17672                resolve_provider: Some(true),
17673                ..Default::default()
17674            }),
17675            ..Default::default()
17676        },
17677        cx,
17678    )
17679    .await;
17680
17681    cx.set_state("fn main() { let a = 2ˇ; }");
17682    cx.simulate_keystroke(".");
17683
17684    let unresolved_item_1 = lsp::CompletionItem {
17685        label: "id".to_string(),
17686        filter_text: Some("id".to_string()),
17687        detail: None,
17688        documentation: None,
17689        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17690            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17691            new_text: ".id".to_string(),
17692        })),
17693        ..lsp::CompletionItem::default()
17694    };
17695    let resolved_item_1 = lsp::CompletionItem {
17696        additional_text_edits: Some(vec![lsp::TextEdit {
17697            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17698            new_text: "!!".to_string(),
17699        }]),
17700        ..unresolved_item_1.clone()
17701    };
17702    let unresolved_item_2 = lsp::CompletionItem {
17703        label: "other".to_string(),
17704        filter_text: Some("other".to_string()),
17705        detail: None,
17706        documentation: None,
17707        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17708            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17709            new_text: ".other".to_string(),
17710        })),
17711        ..lsp::CompletionItem::default()
17712    };
17713    let resolved_item_2 = lsp::CompletionItem {
17714        additional_text_edits: Some(vec![lsp::TextEdit {
17715            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17716            new_text: "??".to_string(),
17717        }]),
17718        ..unresolved_item_2.clone()
17719    };
17720
17721    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17722    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17723    cx.lsp
17724        .server
17725        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17726            let unresolved_item_1 = unresolved_item_1.clone();
17727            let resolved_item_1 = resolved_item_1.clone();
17728            let unresolved_item_2 = unresolved_item_2.clone();
17729            let resolved_item_2 = resolved_item_2.clone();
17730            let resolve_requests_1 = resolve_requests_1.clone();
17731            let resolve_requests_2 = resolve_requests_2.clone();
17732            move |unresolved_request, _| {
17733                let unresolved_item_1 = unresolved_item_1.clone();
17734                let resolved_item_1 = resolved_item_1.clone();
17735                let unresolved_item_2 = unresolved_item_2.clone();
17736                let resolved_item_2 = resolved_item_2.clone();
17737                let resolve_requests_1 = resolve_requests_1.clone();
17738                let resolve_requests_2 = resolve_requests_2.clone();
17739                async move {
17740                    if unresolved_request == unresolved_item_1 {
17741                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17742                        Ok(resolved_item_1.clone())
17743                    } else if unresolved_request == unresolved_item_2 {
17744                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17745                        Ok(resolved_item_2.clone())
17746                    } else {
17747                        panic!("Unexpected completion item {unresolved_request:?}")
17748                    }
17749                }
17750            }
17751        })
17752        .detach();
17753
17754    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17755        let unresolved_item_1 = unresolved_item_1.clone();
17756        let unresolved_item_2 = unresolved_item_2.clone();
17757        async move {
17758            Ok(Some(lsp::CompletionResponse::Array(vec![
17759                unresolved_item_1,
17760                unresolved_item_2,
17761            ])))
17762        }
17763    })
17764    .next()
17765    .await;
17766
17767    cx.condition(|editor, _| editor.context_menu_visible())
17768        .await;
17769    cx.update_editor(|editor, _, _| {
17770        let context_menu = editor.context_menu.borrow_mut();
17771        let context_menu = context_menu
17772            .as_ref()
17773            .expect("Should have the context menu deployed");
17774        match context_menu {
17775            CodeContextMenu::Completions(completions_menu) => {
17776                let completions = completions_menu.completions.borrow_mut();
17777                assert_eq!(
17778                    completions
17779                        .iter()
17780                        .map(|completion| &completion.label.text)
17781                        .collect::<Vec<_>>(),
17782                    vec!["id", "other"]
17783                )
17784            }
17785            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17786        }
17787    });
17788    cx.run_until_parked();
17789
17790    cx.update_editor(|editor, window, cx| {
17791        editor.context_menu_next(&ContextMenuNext, window, cx);
17792    });
17793    cx.run_until_parked();
17794    cx.update_editor(|editor, window, cx| {
17795        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17796    });
17797    cx.run_until_parked();
17798    cx.update_editor(|editor, window, cx| {
17799        editor.context_menu_next(&ContextMenuNext, window, cx);
17800    });
17801    cx.run_until_parked();
17802    cx.update_editor(|editor, window, cx| {
17803        editor
17804            .compose_completion(&ComposeCompletion::default(), window, cx)
17805            .expect("No task returned")
17806    })
17807    .await
17808    .expect("Completion failed");
17809    cx.run_until_parked();
17810
17811    cx.update_editor(|editor, _, cx| {
17812        assert_eq!(
17813            resolve_requests_1.load(atomic::Ordering::Acquire),
17814            1,
17815            "Should always resolve once despite multiple selections"
17816        );
17817        assert_eq!(
17818            resolve_requests_2.load(atomic::Ordering::Acquire),
17819            1,
17820            "Should always resolve once after multiple selections and applying the completion"
17821        );
17822        assert_eq!(
17823            editor.text(cx),
17824            "fn main() { let a = ??.other; }",
17825            "Should use resolved data when applying the completion"
17826        );
17827    });
17828}
17829
17830#[gpui::test]
17831async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17832    init_test(cx, |_| {});
17833
17834    let item_0 = lsp::CompletionItem {
17835        label: "abs".into(),
17836        insert_text: Some("abs".into()),
17837        data: Some(json!({ "very": "special"})),
17838        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17839        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17840            lsp::InsertReplaceEdit {
17841                new_text: "abs".to_string(),
17842                insert: lsp::Range::default(),
17843                replace: lsp::Range::default(),
17844            },
17845        )),
17846        ..lsp::CompletionItem::default()
17847    };
17848    let items = iter::once(item_0.clone())
17849        .chain((11..51).map(|i| lsp::CompletionItem {
17850            label: format!("item_{}", i),
17851            insert_text: Some(format!("item_{}", i)),
17852            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17853            ..lsp::CompletionItem::default()
17854        }))
17855        .collect::<Vec<_>>();
17856
17857    let default_commit_characters = vec!["?".to_string()];
17858    let default_data = json!({ "default": "data"});
17859    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17860    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17861    let default_edit_range = lsp::Range {
17862        start: lsp::Position {
17863            line: 0,
17864            character: 5,
17865        },
17866        end: lsp::Position {
17867            line: 0,
17868            character: 5,
17869        },
17870    };
17871
17872    let mut cx = EditorLspTestContext::new_rust(
17873        lsp::ServerCapabilities {
17874            completion_provider: Some(lsp::CompletionOptions {
17875                trigger_characters: Some(vec![".".to_string()]),
17876                resolve_provider: Some(true),
17877                ..Default::default()
17878            }),
17879            ..Default::default()
17880        },
17881        cx,
17882    )
17883    .await;
17884
17885    cx.set_state("fn main() { let a = 2ˇ; }");
17886    cx.simulate_keystroke(".");
17887
17888    let completion_data = default_data.clone();
17889    let completion_characters = default_commit_characters.clone();
17890    let completion_items = items.clone();
17891    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17892        let default_data = completion_data.clone();
17893        let default_commit_characters = completion_characters.clone();
17894        let items = completion_items.clone();
17895        async move {
17896            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17897                items,
17898                item_defaults: Some(lsp::CompletionListItemDefaults {
17899                    data: Some(default_data.clone()),
17900                    commit_characters: Some(default_commit_characters.clone()),
17901                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17902                        default_edit_range,
17903                    )),
17904                    insert_text_format: Some(default_insert_text_format),
17905                    insert_text_mode: Some(default_insert_text_mode),
17906                }),
17907                ..lsp::CompletionList::default()
17908            })))
17909        }
17910    })
17911    .next()
17912    .await;
17913
17914    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17915    cx.lsp
17916        .server
17917        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17918            let closure_resolved_items = resolved_items.clone();
17919            move |item_to_resolve, _| {
17920                let closure_resolved_items = closure_resolved_items.clone();
17921                async move {
17922                    closure_resolved_items.lock().push(item_to_resolve.clone());
17923                    Ok(item_to_resolve)
17924                }
17925            }
17926        })
17927        .detach();
17928
17929    cx.condition(|editor, _| editor.context_menu_visible())
17930        .await;
17931    cx.run_until_parked();
17932    cx.update_editor(|editor, _, _| {
17933        let menu = editor.context_menu.borrow_mut();
17934        match menu.as_ref().expect("should have the completions menu") {
17935            CodeContextMenu::Completions(completions_menu) => {
17936                assert_eq!(
17937                    completions_menu
17938                        .entries
17939                        .borrow()
17940                        .iter()
17941                        .map(|mat| mat.string.clone())
17942                        .collect::<Vec<String>>(),
17943                    items
17944                        .iter()
17945                        .map(|completion| completion.label.clone())
17946                        .collect::<Vec<String>>()
17947                );
17948            }
17949            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17950        }
17951    });
17952    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17953    // with 4 from the end.
17954    assert_eq!(
17955        *resolved_items.lock(),
17956        [&items[0..16], &items[items.len() - 4..items.len()]]
17957            .concat()
17958            .iter()
17959            .cloned()
17960            .map(|mut item| {
17961                if item.data.is_none() {
17962                    item.data = Some(default_data.clone());
17963                }
17964                item
17965            })
17966            .collect::<Vec<lsp::CompletionItem>>(),
17967        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17968    );
17969    resolved_items.lock().clear();
17970
17971    cx.update_editor(|editor, window, cx| {
17972        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17973    });
17974    cx.run_until_parked();
17975    // Completions that have already been resolved are skipped.
17976    assert_eq!(
17977        *resolved_items.lock(),
17978        items[items.len() - 17..items.len() - 4]
17979            .iter()
17980            .cloned()
17981            .map(|mut item| {
17982                if item.data.is_none() {
17983                    item.data = Some(default_data.clone());
17984                }
17985                item
17986            })
17987            .collect::<Vec<lsp::CompletionItem>>()
17988    );
17989    resolved_items.lock().clear();
17990}
17991
17992#[gpui::test]
17993async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17994    init_test(cx, |_| {});
17995
17996    let mut cx = EditorLspTestContext::new(
17997        Language::new(
17998            LanguageConfig {
17999                matcher: LanguageMatcher {
18000                    path_suffixes: vec!["jsx".into()],
18001                    ..Default::default()
18002                },
18003                overrides: [(
18004                    "element".into(),
18005                    LanguageConfigOverride {
18006                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18007                        ..Default::default()
18008                    },
18009                )]
18010                .into_iter()
18011                .collect(),
18012                ..Default::default()
18013            },
18014            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18015        )
18016        .with_override_query("(jsx_self_closing_element) @element")
18017        .unwrap(),
18018        lsp::ServerCapabilities {
18019            completion_provider: Some(lsp::CompletionOptions {
18020                trigger_characters: Some(vec![":".to_string()]),
18021                ..Default::default()
18022            }),
18023            ..Default::default()
18024        },
18025        cx,
18026    )
18027    .await;
18028
18029    cx.lsp
18030        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18031            Ok(Some(lsp::CompletionResponse::Array(vec![
18032                lsp::CompletionItem {
18033                    label: "bg-blue".into(),
18034                    ..Default::default()
18035                },
18036                lsp::CompletionItem {
18037                    label: "bg-red".into(),
18038                    ..Default::default()
18039                },
18040                lsp::CompletionItem {
18041                    label: "bg-yellow".into(),
18042                    ..Default::default()
18043                },
18044            ])))
18045        });
18046
18047    cx.set_state(r#"<p class="bgˇ" />"#);
18048
18049    // Trigger completion when typing a dash, because the dash is an extra
18050    // word character in the 'element' scope, which contains the cursor.
18051    cx.simulate_keystroke("-");
18052    cx.executor().run_until_parked();
18053    cx.update_editor(|editor, _, _| {
18054        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18055        {
18056            assert_eq!(
18057                completion_menu_entries(menu),
18058                &["bg-blue", "bg-red", "bg-yellow"]
18059            );
18060        } else {
18061            panic!("expected completion menu to be open");
18062        }
18063    });
18064
18065    cx.simulate_keystroke("l");
18066    cx.executor().run_until_parked();
18067    cx.update_editor(|editor, _, _| {
18068        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18069        {
18070            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18071        } else {
18072            panic!("expected completion menu to be open");
18073        }
18074    });
18075
18076    // When filtering completions, consider the character after the '-' to
18077    // be the start of a subword.
18078    cx.set_state(r#"<p class="yelˇ" />"#);
18079    cx.simulate_keystroke("l");
18080    cx.executor().run_until_parked();
18081    cx.update_editor(|editor, _, _| {
18082        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18083        {
18084            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18085        } else {
18086            panic!("expected completion menu to be open");
18087        }
18088    });
18089}
18090
18091fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18092    let entries = menu.entries.borrow();
18093    entries.iter().map(|mat| mat.string.clone()).collect()
18094}
18095
18096#[gpui::test]
18097async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18098    init_test(cx, |settings| {
18099        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
18100    });
18101
18102    let fs = FakeFs::new(cx.executor());
18103    fs.insert_file(path!("/file.ts"), Default::default()).await;
18104
18105    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18106    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18107
18108    language_registry.add(Arc::new(Language::new(
18109        LanguageConfig {
18110            name: "TypeScript".into(),
18111            matcher: LanguageMatcher {
18112                path_suffixes: vec!["ts".to_string()],
18113                ..Default::default()
18114            },
18115            ..Default::default()
18116        },
18117        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18118    )));
18119    update_test_language_settings(cx, |settings| {
18120        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18121    });
18122
18123    let test_plugin = "test_plugin";
18124    let _ = language_registry.register_fake_lsp(
18125        "TypeScript",
18126        FakeLspAdapter {
18127            prettier_plugins: vec![test_plugin],
18128            ..Default::default()
18129        },
18130    );
18131
18132    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18133    let buffer = project
18134        .update(cx, |project, cx| {
18135            project.open_local_buffer(path!("/file.ts"), cx)
18136        })
18137        .await
18138        .unwrap();
18139
18140    let buffer_text = "one\ntwo\nthree\n";
18141    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18142    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18143    editor.update_in(cx, |editor, window, cx| {
18144        editor.set_text(buffer_text, window, cx)
18145    });
18146
18147    editor
18148        .update_in(cx, |editor, window, cx| {
18149            editor.perform_format(
18150                project.clone(),
18151                FormatTrigger::Manual,
18152                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18153                window,
18154                cx,
18155            )
18156        })
18157        .unwrap()
18158        .await;
18159    assert_eq!(
18160        editor.update(cx, |editor, cx| editor.text(cx)),
18161        buffer_text.to_string() + prettier_format_suffix,
18162        "Test prettier formatting was not applied to the original buffer text",
18163    );
18164
18165    update_test_language_settings(cx, |settings| {
18166        settings.defaults.formatter = Some(FormatterList::default())
18167    });
18168    let format = editor.update_in(cx, |editor, window, cx| {
18169        editor.perform_format(
18170            project.clone(),
18171            FormatTrigger::Manual,
18172            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18173            window,
18174            cx,
18175        )
18176    });
18177    format.await.unwrap();
18178    assert_eq!(
18179        editor.update(cx, |editor, cx| editor.text(cx)),
18180        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18181        "Autoformatting (via test prettier) was not applied to the original buffer text",
18182    );
18183}
18184
18185#[gpui::test]
18186async fn test_addition_reverts(cx: &mut TestAppContext) {
18187    init_test(cx, |_| {});
18188    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18189    let base_text = indoc! {r#"
18190        struct Row;
18191        struct Row1;
18192        struct Row2;
18193
18194        struct Row4;
18195        struct Row5;
18196        struct Row6;
18197
18198        struct Row8;
18199        struct Row9;
18200        struct Row10;"#};
18201
18202    // When addition hunks are not adjacent to carets, no hunk revert is performed
18203    assert_hunk_revert(
18204        indoc! {r#"struct Row;
18205                   struct Row1;
18206                   struct Row1.1;
18207                   struct Row1.2;
18208                   struct Row2;ˇ
18209
18210                   struct Row4;
18211                   struct Row5;
18212                   struct Row6;
18213
18214                   struct Row8;
18215                   ˇstruct Row9;
18216                   struct Row9.1;
18217                   struct Row9.2;
18218                   struct Row9.3;
18219                   struct Row10;"#},
18220        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18221        indoc! {r#"struct Row;
18222                   struct Row1;
18223                   struct Row1.1;
18224                   struct Row1.2;
18225                   struct Row2;ˇ
18226
18227                   struct Row4;
18228                   struct Row5;
18229                   struct Row6;
18230
18231                   struct Row8;
18232                   ˇstruct Row9;
18233                   struct Row9.1;
18234                   struct Row9.2;
18235                   struct Row9.3;
18236                   struct Row10;"#},
18237        base_text,
18238        &mut cx,
18239    );
18240    // Same for selections
18241    assert_hunk_revert(
18242        indoc! {r#"struct Row;
18243                   struct Row1;
18244                   struct Row2;
18245                   struct Row2.1;
18246                   struct Row2.2;
18247                   «ˇ
18248                   struct Row4;
18249                   struct» Row5;
18250                   «struct Row6;
18251                   ˇ»
18252                   struct Row9.1;
18253                   struct Row9.2;
18254                   struct Row9.3;
18255                   struct Row8;
18256                   struct Row9;
18257                   struct Row10;"#},
18258        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18259        indoc! {r#"struct Row;
18260                   struct Row1;
18261                   struct Row2;
18262                   struct Row2.1;
18263                   struct Row2.2;
18264                   «ˇ
18265                   struct Row4;
18266                   struct» Row5;
18267                   «struct Row6;
18268                   ˇ»
18269                   struct Row9.1;
18270                   struct Row9.2;
18271                   struct Row9.3;
18272                   struct Row8;
18273                   struct Row9;
18274                   struct Row10;"#},
18275        base_text,
18276        &mut cx,
18277    );
18278
18279    // When carets and selections intersect the addition hunks, those are reverted.
18280    // Adjacent carets got merged.
18281    assert_hunk_revert(
18282        indoc! {r#"struct Row;
18283                   ˇ// something on the top
18284                   struct Row1;
18285                   struct Row2;
18286                   struct Roˇw3.1;
18287                   struct Row2.2;
18288                   struct Row2.3;ˇ
18289
18290                   struct Row4;
18291                   struct ˇRow5.1;
18292                   struct Row5.2;
18293                   struct «Rowˇ»5.3;
18294                   struct Row5;
18295                   struct Row6;
18296                   ˇ
18297                   struct Row9.1;
18298                   struct «Rowˇ»9.2;
18299                   struct «ˇRow»9.3;
18300                   struct Row8;
18301                   struct Row9;
18302                   «ˇ// something on bottom»
18303                   struct Row10;"#},
18304        vec![
18305            DiffHunkStatusKind::Added,
18306            DiffHunkStatusKind::Added,
18307            DiffHunkStatusKind::Added,
18308            DiffHunkStatusKind::Added,
18309            DiffHunkStatusKind::Added,
18310        ],
18311        indoc! {r#"struct Row;
18312                   ˇstruct Row1;
18313                   struct Row2;
18314                   ˇ
18315                   struct Row4;
18316                   ˇstruct Row5;
18317                   struct Row6;
18318                   ˇ
18319                   ˇstruct Row8;
18320                   struct Row9;
18321                   ˇstruct Row10;"#},
18322        base_text,
18323        &mut cx,
18324    );
18325}
18326
18327#[gpui::test]
18328async fn test_modification_reverts(cx: &mut TestAppContext) {
18329    init_test(cx, |_| {});
18330    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18331    let base_text = indoc! {r#"
18332        struct Row;
18333        struct Row1;
18334        struct Row2;
18335
18336        struct Row4;
18337        struct Row5;
18338        struct Row6;
18339
18340        struct Row8;
18341        struct Row9;
18342        struct Row10;"#};
18343
18344    // Modification hunks behave the same as the addition ones.
18345    assert_hunk_revert(
18346        indoc! {r#"struct Row;
18347                   struct Row1;
18348                   struct Row33;
18349                   ˇ
18350                   struct Row4;
18351                   struct Row5;
18352                   struct Row6;
18353                   ˇ
18354                   struct Row99;
18355                   struct Row9;
18356                   struct Row10;"#},
18357        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18358        indoc! {r#"struct Row;
18359                   struct Row1;
18360                   struct Row33;
18361                   ˇ
18362                   struct Row4;
18363                   struct Row5;
18364                   struct Row6;
18365                   ˇ
18366                   struct Row99;
18367                   struct Row9;
18368                   struct Row10;"#},
18369        base_text,
18370        &mut cx,
18371    );
18372    assert_hunk_revert(
18373        indoc! {r#"struct Row;
18374                   struct Row1;
18375                   struct Row33;
18376                   «ˇ
18377                   struct Row4;
18378                   struct» Row5;
18379                   «struct Row6;
18380                   ˇ»
18381                   struct Row99;
18382                   struct Row9;
18383                   struct Row10;"#},
18384        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18385        indoc! {r#"struct Row;
18386                   struct Row1;
18387                   struct Row33;
18388                   «ˇ
18389                   struct Row4;
18390                   struct» Row5;
18391                   «struct Row6;
18392                   ˇ»
18393                   struct Row99;
18394                   struct Row9;
18395                   struct Row10;"#},
18396        base_text,
18397        &mut cx,
18398    );
18399
18400    assert_hunk_revert(
18401        indoc! {r#"ˇstruct Row1.1;
18402                   struct Row1;
18403                   «ˇstr»uct Row22;
18404
18405                   struct ˇRow44;
18406                   struct Row5;
18407                   struct «Rˇ»ow66;ˇ
18408
18409                   «struˇ»ct Row88;
18410                   struct Row9;
18411                   struct Row1011;ˇ"#},
18412        vec![
18413            DiffHunkStatusKind::Modified,
18414            DiffHunkStatusKind::Modified,
18415            DiffHunkStatusKind::Modified,
18416            DiffHunkStatusKind::Modified,
18417            DiffHunkStatusKind::Modified,
18418            DiffHunkStatusKind::Modified,
18419        ],
18420        indoc! {r#"struct Row;
18421                   ˇstruct Row1;
18422                   struct Row2;
18423                   ˇ
18424                   struct Row4;
18425                   ˇstruct Row5;
18426                   struct Row6;
18427                   ˇ
18428                   struct Row8;
18429                   ˇstruct Row9;
18430                   struct Row10;ˇ"#},
18431        base_text,
18432        &mut cx,
18433    );
18434}
18435
18436#[gpui::test]
18437async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18438    init_test(cx, |_| {});
18439    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18440    let base_text = indoc! {r#"
18441        one
18442
18443        two
18444        three
18445        "#};
18446
18447    cx.set_head_text(base_text);
18448    cx.set_state("\nˇ\n");
18449    cx.executor().run_until_parked();
18450    cx.update_editor(|editor, _window, cx| {
18451        editor.expand_selected_diff_hunks(cx);
18452    });
18453    cx.executor().run_until_parked();
18454    cx.update_editor(|editor, window, cx| {
18455        editor.backspace(&Default::default(), window, cx);
18456    });
18457    cx.run_until_parked();
18458    cx.assert_state_with_diff(
18459        indoc! {r#"
18460
18461        - two
18462        - threeˇ
18463        +
18464        "#}
18465        .to_string(),
18466    );
18467}
18468
18469#[gpui::test]
18470async fn test_deletion_reverts(cx: &mut TestAppContext) {
18471    init_test(cx, |_| {});
18472    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18473    let base_text = indoc! {r#"struct Row;
18474struct Row1;
18475struct Row2;
18476
18477struct Row4;
18478struct Row5;
18479struct Row6;
18480
18481struct Row8;
18482struct Row9;
18483struct Row10;"#};
18484
18485    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18486    assert_hunk_revert(
18487        indoc! {r#"struct Row;
18488                   struct Row2;
18489
18490                   ˇstruct Row4;
18491                   struct Row5;
18492                   struct Row6;
18493                   ˇ
18494                   struct Row8;
18495                   struct Row10;"#},
18496        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18497        indoc! {r#"struct Row;
18498                   struct Row2;
18499
18500                   ˇstruct Row4;
18501                   struct Row5;
18502                   struct Row6;
18503                   ˇ
18504                   struct Row8;
18505                   struct Row10;"#},
18506        base_text,
18507        &mut cx,
18508    );
18509    assert_hunk_revert(
18510        indoc! {r#"struct Row;
18511                   struct Row2;
18512
18513                   «ˇstruct Row4;
18514                   struct» Row5;
18515                   «struct Row6;
18516                   ˇ»
18517                   struct Row8;
18518                   struct Row10;"#},
18519        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18520        indoc! {r#"struct Row;
18521                   struct Row2;
18522
18523                   «ˇstruct Row4;
18524                   struct» Row5;
18525                   «struct Row6;
18526                   ˇ»
18527                   struct Row8;
18528                   struct Row10;"#},
18529        base_text,
18530        &mut cx,
18531    );
18532
18533    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18534    assert_hunk_revert(
18535        indoc! {r#"struct Row;
18536                   ˇstruct Row2;
18537
18538                   struct Row4;
18539                   struct Row5;
18540                   struct Row6;
18541
18542                   struct Row8;ˇ
18543                   struct Row10;"#},
18544        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18545        indoc! {r#"struct Row;
18546                   struct Row1;
18547                   ˇstruct Row2;
18548
18549                   struct Row4;
18550                   struct Row5;
18551                   struct Row6;
18552
18553                   struct Row8;ˇ
18554                   struct Row9;
18555                   struct Row10;"#},
18556        base_text,
18557        &mut cx,
18558    );
18559    assert_hunk_revert(
18560        indoc! {r#"struct Row;
18561                   struct Row2«ˇ;
18562                   struct Row4;
18563                   struct» Row5;
18564                   «struct Row6;
18565
18566                   struct Row8;ˇ»
18567                   struct Row10;"#},
18568        vec![
18569            DiffHunkStatusKind::Deleted,
18570            DiffHunkStatusKind::Deleted,
18571            DiffHunkStatusKind::Deleted,
18572        ],
18573        indoc! {r#"struct Row;
18574                   struct Row1;
18575                   struct Row2«ˇ;
18576
18577                   struct Row4;
18578                   struct» Row5;
18579                   «struct Row6;
18580
18581                   struct Row8;ˇ»
18582                   struct Row9;
18583                   struct Row10;"#},
18584        base_text,
18585        &mut cx,
18586    );
18587}
18588
18589#[gpui::test]
18590async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18591    init_test(cx, |_| {});
18592
18593    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18594    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18595    let base_text_3 =
18596        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18597
18598    let text_1 = edit_first_char_of_every_line(base_text_1);
18599    let text_2 = edit_first_char_of_every_line(base_text_2);
18600    let text_3 = edit_first_char_of_every_line(base_text_3);
18601
18602    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18603    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18604    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18605
18606    let multibuffer = cx.new(|cx| {
18607        let mut multibuffer = MultiBuffer::new(ReadWrite);
18608        multibuffer.push_excerpts(
18609            buffer_1.clone(),
18610            [
18611                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18612                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18613                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18614            ],
18615            cx,
18616        );
18617        multibuffer.push_excerpts(
18618            buffer_2.clone(),
18619            [
18620                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18621                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18622                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18623            ],
18624            cx,
18625        );
18626        multibuffer.push_excerpts(
18627            buffer_3.clone(),
18628            [
18629                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18630                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18631                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18632            ],
18633            cx,
18634        );
18635        multibuffer
18636    });
18637
18638    let fs = FakeFs::new(cx.executor());
18639    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18640    let (editor, cx) = cx
18641        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18642    editor.update_in(cx, |editor, _window, cx| {
18643        for (buffer, diff_base) in [
18644            (buffer_1.clone(), base_text_1),
18645            (buffer_2.clone(), base_text_2),
18646            (buffer_3.clone(), base_text_3),
18647        ] {
18648            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18649            editor
18650                .buffer
18651                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18652        }
18653    });
18654    cx.executor().run_until_parked();
18655
18656    editor.update_in(cx, |editor, window, cx| {
18657        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}");
18658        editor.select_all(&SelectAll, window, cx);
18659        editor.git_restore(&Default::default(), window, cx);
18660    });
18661    cx.executor().run_until_parked();
18662
18663    // When all ranges are selected, all buffer hunks are reverted.
18664    editor.update(cx, |editor, cx| {
18665        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");
18666    });
18667    buffer_1.update(cx, |buffer, _| {
18668        assert_eq!(buffer.text(), base_text_1);
18669    });
18670    buffer_2.update(cx, |buffer, _| {
18671        assert_eq!(buffer.text(), base_text_2);
18672    });
18673    buffer_3.update(cx, |buffer, _| {
18674        assert_eq!(buffer.text(), base_text_3);
18675    });
18676
18677    editor.update_in(cx, |editor, window, cx| {
18678        editor.undo(&Default::default(), window, cx);
18679    });
18680
18681    editor.update_in(cx, |editor, window, cx| {
18682        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18683            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18684        });
18685        editor.git_restore(&Default::default(), window, cx);
18686    });
18687
18688    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18689    // but not affect buffer_2 and its related excerpts.
18690    editor.update(cx, |editor, cx| {
18691        assert_eq!(
18692            editor.text(cx),
18693            "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}"
18694        );
18695    });
18696    buffer_1.update(cx, |buffer, _| {
18697        assert_eq!(buffer.text(), base_text_1);
18698    });
18699    buffer_2.update(cx, |buffer, _| {
18700        assert_eq!(
18701            buffer.text(),
18702            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18703        );
18704    });
18705    buffer_3.update(cx, |buffer, _| {
18706        assert_eq!(
18707            buffer.text(),
18708            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18709        );
18710    });
18711
18712    fn edit_first_char_of_every_line(text: &str) -> String {
18713        text.split('\n')
18714            .map(|line| format!("X{}", &line[1..]))
18715            .collect::<Vec<_>>()
18716            .join("\n")
18717    }
18718}
18719
18720#[gpui::test]
18721async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18722    init_test(cx, |_| {});
18723
18724    let cols = 4;
18725    let rows = 10;
18726    let sample_text_1 = sample_text(rows, cols, 'a');
18727    assert_eq!(
18728        sample_text_1,
18729        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18730    );
18731    let sample_text_2 = sample_text(rows, cols, 'l');
18732    assert_eq!(
18733        sample_text_2,
18734        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18735    );
18736    let sample_text_3 = sample_text(rows, cols, 'v');
18737    assert_eq!(
18738        sample_text_3,
18739        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18740    );
18741
18742    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18743    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18744    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18745
18746    let multi_buffer = cx.new(|cx| {
18747        let mut multibuffer = MultiBuffer::new(ReadWrite);
18748        multibuffer.push_excerpts(
18749            buffer_1.clone(),
18750            [
18751                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18752                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18753                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18754            ],
18755            cx,
18756        );
18757        multibuffer.push_excerpts(
18758            buffer_2.clone(),
18759            [
18760                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18761                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18762                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18763            ],
18764            cx,
18765        );
18766        multibuffer.push_excerpts(
18767            buffer_3.clone(),
18768            [
18769                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18770                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18771                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18772            ],
18773            cx,
18774        );
18775        multibuffer
18776    });
18777
18778    let fs = FakeFs::new(cx.executor());
18779    fs.insert_tree(
18780        "/a",
18781        json!({
18782            "main.rs": sample_text_1,
18783            "other.rs": sample_text_2,
18784            "lib.rs": sample_text_3,
18785        }),
18786    )
18787    .await;
18788    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18789    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18790    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18791    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18792        Editor::new(
18793            EditorMode::full(),
18794            multi_buffer,
18795            Some(project.clone()),
18796            window,
18797            cx,
18798        )
18799    });
18800    let multibuffer_item_id = workspace
18801        .update(cx, |workspace, window, cx| {
18802            assert!(
18803                workspace.active_item(cx).is_none(),
18804                "active item should be None before the first item is added"
18805            );
18806            workspace.add_item_to_active_pane(
18807                Box::new(multi_buffer_editor.clone()),
18808                None,
18809                true,
18810                window,
18811                cx,
18812            );
18813            let active_item = workspace
18814                .active_item(cx)
18815                .expect("should have an active item after adding the multi buffer");
18816            assert_eq!(
18817                active_item.buffer_kind(cx),
18818                ItemBufferKind::Multibuffer,
18819                "A multi buffer was expected to active after adding"
18820            );
18821            active_item.item_id()
18822        })
18823        .unwrap();
18824    cx.executor().run_until_parked();
18825
18826    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18827        editor.change_selections(
18828            SelectionEffects::scroll(Autoscroll::Next),
18829            window,
18830            cx,
18831            |s| s.select_ranges(Some(1..2)),
18832        );
18833        editor.open_excerpts(&OpenExcerpts, window, cx);
18834    });
18835    cx.executor().run_until_parked();
18836    let first_item_id = workspace
18837        .update(cx, |workspace, window, cx| {
18838            let active_item = workspace
18839                .active_item(cx)
18840                .expect("should have an active item after navigating into the 1st buffer");
18841            let first_item_id = active_item.item_id();
18842            assert_ne!(
18843                first_item_id, multibuffer_item_id,
18844                "Should navigate into the 1st buffer and activate it"
18845            );
18846            assert_eq!(
18847                active_item.buffer_kind(cx),
18848                ItemBufferKind::Singleton,
18849                "New active item should be a singleton buffer"
18850            );
18851            assert_eq!(
18852                active_item
18853                    .act_as::<Editor>(cx)
18854                    .expect("should have navigated into an editor for the 1st buffer")
18855                    .read(cx)
18856                    .text(cx),
18857                sample_text_1
18858            );
18859
18860            workspace
18861                .go_back(workspace.active_pane().downgrade(), window, cx)
18862                .detach_and_log_err(cx);
18863
18864            first_item_id
18865        })
18866        .unwrap();
18867    cx.executor().run_until_parked();
18868    workspace
18869        .update(cx, |workspace, _, cx| {
18870            let active_item = workspace
18871                .active_item(cx)
18872                .expect("should have an active item after navigating back");
18873            assert_eq!(
18874                active_item.item_id(),
18875                multibuffer_item_id,
18876                "Should navigate back to the multi buffer"
18877            );
18878            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
18879        })
18880        .unwrap();
18881
18882    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18883        editor.change_selections(
18884            SelectionEffects::scroll(Autoscroll::Next),
18885            window,
18886            cx,
18887            |s| s.select_ranges(Some(39..40)),
18888        );
18889        editor.open_excerpts(&OpenExcerpts, window, cx);
18890    });
18891    cx.executor().run_until_parked();
18892    let second_item_id = workspace
18893        .update(cx, |workspace, window, cx| {
18894            let active_item = workspace
18895                .active_item(cx)
18896                .expect("should have an active item after navigating into the 2nd buffer");
18897            let second_item_id = active_item.item_id();
18898            assert_ne!(
18899                second_item_id, multibuffer_item_id,
18900                "Should navigate away from the multibuffer"
18901            );
18902            assert_ne!(
18903                second_item_id, first_item_id,
18904                "Should navigate into the 2nd buffer and activate it"
18905            );
18906            assert_eq!(
18907                active_item.buffer_kind(cx),
18908                ItemBufferKind::Singleton,
18909                "New active item should be a singleton buffer"
18910            );
18911            assert_eq!(
18912                active_item
18913                    .act_as::<Editor>(cx)
18914                    .expect("should have navigated into an editor")
18915                    .read(cx)
18916                    .text(cx),
18917                sample_text_2
18918            );
18919
18920            workspace
18921                .go_back(workspace.active_pane().downgrade(), window, cx)
18922                .detach_and_log_err(cx);
18923
18924            second_item_id
18925        })
18926        .unwrap();
18927    cx.executor().run_until_parked();
18928    workspace
18929        .update(cx, |workspace, _, cx| {
18930            let active_item = workspace
18931                .active_item(cx)
18932                .expect("should have an active item after navigating back from the 2nd buffer");
18933            assert_eq!(
18934                active_item.item_id(),
18935                multibuffer_item_id,
18936                "Should navigate back from the 2nd buffer to the multi buffer"
18937            );
18938            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
18939        })
18940        .unwrap();
18941
18942    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18943        editor.change_selections(
18944            SelectionEffects::scroll(Autoscroll::Next),
18945            window,
18946            cx,
18947            |s| s.select_ranges(Some(70..70)),
18948        );
18949        editor.open_excerpts(&OpenExcerpts, window, cx);
18950    });
18951    cx.executor().run_until_parked();
18952    workspace
18953        .update(cx, |workspace, window, cx| {
18954            let active_item = workspace
18955                .active_item(cx)
18956                .expect("should have an active item after navigating into the 3rd buffer");
18957            let third_item_id = active_item.item_id();
18958            assert_ne!(
18959                third_item_id, multibuffer_item_id,
18960                "Should navigate into the 3rd buffer and activate it"
18961            );
18962            assert_ne!(third_item_id, first_item_id);
18963            assert_ne!(third_item_id, second_item_id);
18964            assert_eq!(
18965                active_item.buffer_kind(cx),
18966                ItemBufferKind::Singleton,
18967                "New active item should be a singleton buffer"
18968            );
18969            assert_eq!(
18970                active_item
18971                    .act_as::<Editor>(cx)
18972                    .expect("should have navigated into an editor")
18973                    .read(cx)
18974                    .text(cx),
18975                sample_text_3
18976            );
18977
18978            workspace
18979                .go_back(workspace.active_pane().downgrade(), window, cx)
18980                .detach_and_log_err(cx);
18981        })
18982        .unwrap();
18983    cx.executor().run_until_parked();
18984    workspace
18985        .update(cx, |workspace, _, cx| {
18986            let active_item = workspace
18987                .active_item(cx)
18988                .expect("should have an active item after navigating back from the 3rd buffer");
18989            assert_eq!(
18990                active_item.item_id(),
18991                multibuffer_item_id,
18992                "Should navigate back from the 3rd buffer to the multi buffer"
18993            );
18994            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
18995        })
18996        .unwrap();
18997}
18998
18999#[gpui::test]
19000async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19001    init_test(cx, |_| {});
19002
19003    let mut cx = EditorTestContext::new(cx).await;
19004
19005    let diff_base = r#"
19006        use some::mod;
19007
19008        const A: u32 = 42;
19009
19010        fn main() {
19011            println!("hello");
19012
19013            println!("world");
19014        }
19015        "#
19016    .unindent();
19017
19018    cx.set_state(
19019        &r#"
19020        use some::modified;
19021
19022        ˇ
19023        fn main() {
19024            println!("hello there");
19025
19026            println!("around the");
19027            println!("world");
19028        }
19029        "#
19030        .unindent(),
19031    );
19032
19033    cx.set_head_text(&diff_base);
19034    executor.run_until_parked();
19035
19036    cx.update_editor(|editor, window, cx| {
19037        editor.go_to_next_hunk(&GoToHunk, window, cx);
19038        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19039    });
19040    executor.run_until_parked();
19041    cx.assert_state_with_diff(
19042        r#"
19043          use some::modified;
19044
19045
19046          fn main() {
19047        -     println!("hello");
19048        + ˇ    println!("hello there");
19049
19050              println!("around the");
19051              println!("world");
19052          }
19053        "#
19054        .unindent(),
19055    );
19056
19057    cx.update_editor(|editor, window, cx| {
19058        for _ in 0..2 {
19059            editor.go_to_next_hunk(&GoToHunk, window, cx);
19060            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19061        }
19062    });
19063    executor.run_until_parked();
19064    cx.assert_state_with_diff(
19065        r#"
19066        - use some::mod;
19067        + ˇuse some::modified;
19068
19069
19070          fn main() {
19071        -     println!("hello");
19072        +     println!("hello there");
19073
19074        +     println!("around the");
19075              println!("world");
19076          }
19077        "#
19078        .unindent(),
19079    );
19080
19081    cx.update_editor(|editor, window, cx| {
19082        editor.go_to_next_hunk(&GoToHunk, window, cx);
19083        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19084    });
19085    executor.run_until_parked();
19086    cx.assert_state_with_diff(
19087        r#"
19088        - use some::mod;
19089        + use some::modified;
19090
19091        - const A: u32 = 42;
19092          ˇ
19093          fn main() {
19094        -     println!("hello");
19095        +     println!("hello there");
19096
19097        +     println!("around the");
19098              println!("world");
19099          }
19100        "#
19101        .unindent(),
19102    );
19103
19104    cx.update_editor(|editor, window, cx| {
19105        editor.cancel(&Cancel, window, cx);
19106    });
19107
19108    cx.assert_state_with_diff(
19109        r#"
19110          use some::modified;
19111
19112          ˇ
19113          fn main() {
19114              println!("hello there");
19115
19116              println!("around the");
19117              println!("world");
19118          }
19119        "#
19120        .unindent(),
19121    );
19122}
19123
19124#[gpui::test]
19125async fn test_diff_base_change_with_expanded_diff_hunks(
19126    executor: BackgroundExecutor,
19127    cx: &mut TestAppContext,
19128) {
19129    init_test(cx, |_| {});
19130
19131    let mut cx = EditorTestContext::new(cx).await;
19132
19133    let diff_base = r#"
19134        use some::mod1;
19135        use some::mod2;
19136
19137        const A: u32 = 42;
19138        const B: u32 = 42;
19139        const C: u32 = 42;
19140
19141        fn main() {
19142            println!("hello");
19143
19144            println!("world");
19145        }
19146        "#
19147    .unindent();
19148
19149    cx.set_state(
19150        &r#"
19151        use some::mod2;
19152
19153        const A: u32 = 42;
19154        const C: u32 = 42;
19155
19156        fn main(ˇ) {
19157            //println!("hello");
19158
19159            println!("world");
19160            //
19161            //
19162        }
19163        "#
19164        .unindent(),
19165    );
19166
19167    cx.set_head_text(&diff_base);
19168    executor.run_until_parked();
19169
19170    cx.update_editor(|editor, window, cx| {
19171        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19172    });
19173    executor.run_until_parked();
19174    cx.assert_state_with_diff(
19175        r#"
19176        - use some::mod1;
19177          use some::mod2;
19178
19179          const A: u32 = 42;
19180        - const B: u32 = 42;
19181          const C: u32 = 42;
19182
19183          fn main(ˇ) {
19184        -     println!("hello");
19185        +     //println!("hello");
19186
19187              println!("world");
19188        +     //
19189        +     //
19190          }
19191        "#
19192        .unindent(),
19193    );
19194
19195    cx.set_head_text("new diff base!");
19196    executor.run_until_parked();
19197    cx.assert_state_with_diff(
19198        r#"
19199        - new diff base!
19200        + use some::mod2;
19201        +
19202        + const A: u32 = 42;
19203        + const C: u32 = 42;
19204        +
19205        + fn main(ˇ) {
19206        +     //println!("hello");
19207        +
19208        +     println!("world");
19209        +     //
19210        +     //
19211        + }
19212        "#
19213        .unindent(),
19214    );
19215}
19216
19217#[gpui::test]
19218async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19219    init_test(cx, |_| {});
19220
19221    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19222    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19223    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19224    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19225    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19226    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19227
19228    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19229    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19230    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19231
19232    let multi_buffer = cx.new(|cx| {
19233        let mut multibuffer = MultiBuffer::new(ReadWrite);
19234        multibuffer.push_excerpts(
19235            buffer_1.clone(),
19236            [
19237                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19238                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19239                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19240            ],
19241            cx,
19242        );
19243        multibuffer.push_excerpts(
19244            buffer_2.clone(),
19245            [
19246                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19247                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19248                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19249            ],
19250            cx,
19251        );
19252        multibuffer.push_excerpts(
19253            buffer_3.clone(),
19254            [
19255                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19256                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19257                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19258            ],
19259            cx,
19260        );
19261        multibuffer
19262    });
19263
19264    let editor =
19265        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19266    editor
19267        .update(cx, |editor, _window, cx| {
19268            for (buffer, diff_base) in [
19269                (buffer_1.clone(), file_1_old),
19270                (buffer_2.clone(), file_2_old),
19271                (buffer_3.clone(), file_3_old),
19272            ] {
19273                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19274                editor
19275                    .buffer
19276                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19277            }
19278        })
19279        .unwrap();
19280
19281    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19282    cx.run_until_parked();
19283
19284    cx.assert_editor_state(
19285        &"
19286            ˇaaa
19287            ccc
19288            ddd
19289
19290            ggg
19291            hhh
19292
19293
19294            lll
19295            mmm
19296            NNN
19297
19298            qqq
19299            rrr
19300
19301            uuu
19302            111
19303            222
19304            333
19305
19306            666
19307            777
19308
19309            000
19310            !!!"
19311        .unindent(),
19312    );
19313
19314    cx.update_editor(|editor, window, cx| {
19315        editor.select_all(&SelectAll, window, cx);
19316        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19317    });
19318    cx.executor().run_until_parked();
19319
19320    cx.assert_state_with_diff(
19321        "
19322            «aaa
19323          - bbb
19324            ccc
19325            ddd
19326
19327            ggg
19328            hhh
19329
19330
19331            lll
19332            mmm
19333          - nnn
19334          + NNN
19335
19336            qqq
19337            rrr
19338
19339            uuu
19340            111
19341            222
19342            333
19343
19344          + 666
19345            777
19346
19347            000
19348            !!!ˇ»"
19349            .unindent(),
19350    );
19351}
19352
19353#[gpui::test]
19354async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19355    init_test(cx, |_| {});
19356
19357    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19358    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19359
19360    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19361    let multi_buffer = cx.new(|cx| {
19362        let mut multibuffer = MultiBuffer::new(ReadWrite);
19363        multibuffer.push_excerpts(
19364            buffer.clone(),
19365            [
19366                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19367                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19368                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19369            ],
19370            cx,
19371        );
19372        multibuffer
19373    });
19374
19375    let editor =
19376        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19377    editor
19378        .update(cx, |editor, _window, cx| {
19379            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19380            editor
19381                .buffer
19382                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19383        })
19384        .unwrap();
19385
19386    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19387    cx.run_until_parked();
19388
19389    cx.update_editor(|editor, window, cx| {
19390        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19391    });
19392    cx.executor().run_until_parked();
19393
19394    // When the start of a hunk coincides with the start of its excerpt,
19395    // the hunk is expanded. When the start of a hunk is earlier than
19396    // the start of its excerpt, the hunk is not expanded.
19397    cx.assert_state_with_diff(
19398        "
19399            ˇaaa
19400          - bbb
19401          + BBB
19402
19403          - ddd
19404          - eee
19405          + DDD
19406          + EEE
19407            fff
19408
19409            iii
19410        "
19411        .unindent(),
19412    );
19413}
19414
19415#[gpui::test]
19416async fn test_edits_around_expanded_insertion_hunks(
19417    executor: BackgroundExecutor,
19418    cx: &mut TestAppContext,
19419) {
19420    init_test(cx, |_| {});
19421
19422    let mut cx = EditorTestContext::new(cx).await;
19423
19424    let diff_base = r#"
19425        use some::mod1;
19426        use some::mod2;
19427
19428        const A: u32 = 42;
19429
19430        fn main() {
19431            println!("hello");
19432
19433            println!("world");
19434        }
19435        "#
19436    .unindent();
19437    executor.run_until_parked();
19438    cx.set_state(
19439        &r#"
19440        use some::mod1;
19441        use some::mod2;
19442
19443        const A: u32 = 42;
19444        const B: u32 = 42;
19445        const C: u32 = 42;
19446        ˇ
19447
19448        fn main() {
19449            println!("hello");
19450
19451            println!("world");
19452        }
19453        "#
19454        .unindent(),
19455    );
19456
19457    cx.set_head_text(&diff_base);
19458    executor.run_until_parked();
19459
19460    cx.update_editor(|editor, window, cx| {
19461        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19462    });
19463    executor.run_until_parked();
19464
19465    cx.assert_state_with_diff(
19466        r#"
19467        use some::mod1;
19468        use some::mod2;
19469
19470        const A: u32 = 42;
19471      + const B: u32 = 42;
19472      + const C: u32 = 42;
19473      + ˇ
19474
19475        fn main() {
19476            println!("hello");
19477
19478            println!("world");
19479        }
19480      "#
19481        .unindent(),
19482    );
19483
19484    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19485    executor.run_until_parked();
19486
19487    cx.assert_state_with_diff(
19488        r#"
19489        use some::mod1;
19490        use some::mod2;
19491
19492        const A: u32 = 42;
19493      + const B: u32 = 42;
19494      + const C: u32 = 42;
19495      + const D: u32 = 42;
19496      + ˇ
19497
19498        fn main() {
19499            println!("hello");
19500
19501            println!("world");
19502        }
19503      "#
19504        .unindent(),
19505    );
19506
19507    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19508    executor.run_until_parked();
19509
19510    cx.assert_state_with_diff(
19511        r#"
19512        use some::mod1;
19513        use some::mod2;
19514
19515        const A: u32 = 42;
19516      + const B: u32 = 42;
19517      + const C: u32 = 42;
19518      + const D: u32 = 42;
19519      + const E: u32 = 42;
19520      + ˇ
19521
19522        fn main() {
19523            println!("hello");
19524
19525            println!("world");
19526        }
19527      "#
19528        .unindent(),
19529    );
19530
19531    cx.update_editor(|editor, window, cx| {
19532        editor.delete_line(&DeleteLine, window, cx);
19533    });
19534    executor.run_until_parked();
19535
19536    cx.assert_state_with_diff(
19537        r#"
19538        use some::mod1;
19539        use some::mod2;
19540
19541        const A: u32 = 42;
19542      + const B: u32 = 42;
19543      + const C: u32 = 42;
19544      + const D: u32 = 42;
19545      + const E: u32 = 42;
19546        ˇ
19547        fn main() {
19548            println!("hello");
19549
19550            println!("world");
19551        }
19552      "#
19553        .unindent(),
19554    );
19555
19556    cx.update_editor(|editor, window, cx| {
19557        editor.move_up(&MoveUp, window, cx);
19558        editor.delete_line(&DeleteLine, window, cx);
19559        editor.move_up(&MoveUp, window, cx);
19560        editor.delete_line(&DeleteLine, window, cx);
19561        editor.move_up(&MoveUp, window, cx);
19562        editor.delete_line(&DeleteLine, window, cx);
19563    });
19564    executor.run_until_parked();
19565    cx.assert_state_with_diff(
19566        r#"
19567        use some::mod1;
19568        use some::mod2;
19569
19570        const A: u32 = 42;
19571      + const B: u32 = 42;
19572        ˇ
19573        fn main() {
19574            println!("hello");
19575
19576            println!("world");
19577        }
19578      "#
19579        .unindent(),
19580    );
19581
19582    cx.update_editor(|editor, window, cx| {
19583        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19584        editor.delete_line(&DeleteLine, window, cx);
19585    });
19586    executor.run_until_parked();
19587    cx.assert_state_with_diff(
19588        r#"
19589        ˇ
19590        fn main() {
19591            println!("hello");
19592
19593            println!("world");
19594        }
19595      "#
19596        .unindent(),
19597    );
19598}
19599
19600#[gpui::test]
19601async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19602    init_test(cx, |_| {});
19603
19604    let mut cx = EditorTestContext::new(cx).await;
19605    cx.set_head_text(indoc! { "
19606        one
19607        two
19608        three
19609        four
19610        five
19611        "
19612    });
19613    cx.set_state(indoc! { "
19614        one
19615        ˇthree
19616        five
19617    "});
19618    cx.run_until_parked();
19619    cx.update_editor(|editor, window, cx| {
19620        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19621    });
19622    cx.assert_state_with_diff(
19623        indoc! { "
19624        one
19625      - two
19626        ˇthree
19627      - four
19628        five
19629    "}
19630        .to_string(),
19631    );
19632    cx.update_editor(|editor, window, cx| {
19633        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19634    });
19635
19636    cx.assert_state_with_diff(
19637        indoc! { "
19638        one
19639        ˇthree
19640        five
19641    "}
19642        .to_string(),
19643    );
19644
19645    cx.set_state(indoc! { "
19646        one
19647        ˇTWO
19648        three
19649        four
19650        five
19651    "});
19652    cx.run_until_parked();
19653    cx.update_editor(|editor, window, cx| {
19654        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19655    });
19656
19657    cx.assert_state_with_diff(
19658        indoc! { "
19659            one
19660          - two
19661          + ˇTWO
19662            three
19663            four
19664            five
19665        "}
19666        .to_string(),
19667    );
19668    cx.update_editor(|editor, window, cx| {
19669        editor.move_up(&Default::default(), window, cx);
19670        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19671    });
19672    cx.assert_state_with_diff(
19673        indoc! { "
19674            one
19675            ˇTWO
19676            three
19677            four
19678            five
19679        "}
19680        .to_string(),
19681    );
19682}
19683
19684#[gpui::test]
19685async fn test_edits_around_expanded_deletion_hunks(
19686    executor: BackgroundExecutor,
19687    cx: &mut TestAppContext,
19688) {
19689    init_test(cx, |_| {});
19690
19691    let mut cx = EditorTestContext::new(cx).await;
19692
19693    let diff_base = r#"
19694        use some::mod1;
19695        use some::mod2;
19696
19697        const A: u32 = 42;
19698        const B: u32 = 42;
19699        const C: u32 = 42;
19700
19701
19702        fn main() {
19703            println!("hello");
19704
19705            println!("world");
19706        }
19707    "#
19708    .unindent();
19709    executor.run_until_parked();
19710    cx.set_state(
19711        &r#"
19712        use some::mod1;
19713        use some::mod2;
19714
19715        ˇconst B: u32 = 42;
19716        const C: u32 = 42;
19717
19718
19719        fn main() {
19720            println!("hello");
19721
19722            println!("world");
19723        }
19724        "#
19725        .unindent(),
19726    );
19727
19728    cx.set_head_text(&diff_base);
19729    executor.run_until_parked();
19730
19731    cx.update_editor(|editor, window, cx| {
19732        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19733    });
19734    executor.run_until_parked();
19735
19736    cx.assert_state_with_diff(
19737        r#"
19738        use some::mod1;
19739        use some::mod2;
19740
19741      - const A: u32 = 42;
19742        ˇconst B: u32 = 42;
19743        const C: u32 = 42;
19744
19745
19746        fn main() {
19747            println!("hello");
19748
19749            println!("world");
19750        }
19751      "#
19752        .unindent(),
19753    );
19754
19755    cx.update_editor(|editor, window, cx| {
19756        editor.delete_line(&DeleteLine, window, cx);
19757    });
19758    executor.run_until_parked();
19759    cx.assert_state_with_diff(
19760        r#"
19761        use some::mod1;
19762        use some::mod2;
19763
19764      - const A: u32 = 42;
19765      - const B: u32 = 42;
19766        ˇconst C: u32 = 42;
19767
19768
19769        fn main() {
19770            println!("hello");
19771
19772            println!("world");
19773        }
19774      "#
19775        .unindent(),
19776    );
19777
19778    cx.update_editor(|editor, window, cx| {
19779        editor.delete_line(&DeleteLine, window, cx);
19780    });
19781    executor.run_until_parked();
19782    cx.assert_state_with_diff(
19783        r#"
19784        use some::mod1;
19785        use some::mod2;
19786
19787      - const A: u32 = 42;
19788      - const B: u32 = 42;
19789      - const C: u32 = 42;
19790        ˇ
19791
19792        fn main() {
19793            println!("hello");
19794
19795            println!("world");
19796        }
19797      "#
19798        .unindent(),
19799    );
19800
19801    cx.update_editor(|editor, window, cx| {
19802        editor.handle_input("replacement", window, cx);
19803    });
19804    executor.run_until_parked();
19805    cx.assert_state_with_diff(
19806        r#"
19807        use some::mod1;
19808        use some::mod2;
19809
19810      - const A: u32 = 42;
19811      - const B: u32 = 42;
19812      - const C: u32 = 42;
19813      -
19814      + replacementˇ
19815
19816        fn main() {
19817            println!("hello");
19818
19819            println!("world");
19820        }
19821      "#
19822        .unindent(),
19823    );
19824}
19825
19826#[gpui::test]
19827async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19828    init_test(cx, |_| {});
19829
19830    let mut cx = EditorTestContext::new(cx).await;
19831
19832    let base_text = r#"
19833        one
19834        two
19835        three
19836        four
19837        five
19838    "#
19839    .unindent();
19840    executor.run_until_parked();
19841    cx.set_state(
19842        &r#"
19843        one
19844        two
19845        fˇour
19846        five
19847        "#
19848        .unindent(),
19849    );
19850
19851    cx.set_head_text(&base_text);
19852    executor.run_until_parked();
19853
19854    cx.update_editor(|editor, window, cx| {
19855        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19856    });
19857    executor.run_until_parked();
19858
19859    cx.assert_state_with_diff(
19860        r#"
19861          one
19862          two
19863        - three
19864          fˇour
19865          five
19866        "#
19867        .unindent(),
19868    );
19869
19870    cx.update_editor(|editor, window, cx| {
19871        editor.backspace(&Backspace, window, cx);
19872        editor.backspace(&Backspace, window, cx);
19873    });
19874    executor.run_until_parked();
19875    cx.assert_state_with_diff(
19876        r#"
19877          one
19878          two
19879        - threeˇ
19880        - four
19881        + our
19882          five
19883        "#
19884        .unindent(),
19885    );
19886}
19887
19888#[gpui::test]
19889async fn test_edit_after_expanded_modification_hunk(
19890    executor: BackgroundExecutor,
19891    cx: &mut TestAppContext,
19892) {
19893    init_test(cx, |_| {});
19894
19895    let mut cx = EditorTestContext::new(cx).await;
19896
19897    let diff_base = r#"
19898        use some::mod1;
19899        use some::mod2;
19900
19901        const A: u32 = 42;
19902        const B: u32 = 42;
19903        const C: u32 = 42;
19904        const D: u32 = 42;
19905
19906
19907        fn main() {
19908            println!("hello");
19909
19910            println!("world");
19911        }"#
19912    .unindent();
19913
19914    cx.set_state(
19915        &r#"
19916        use some::mod1;
19917        use some::mod2;
19918
19919        const A: u32 = 42;
19920        const B: u32 = 42;
19921        const C: u32 = 43ˇ
19922        const D: u32 = 42;
19923
19924
19925        fn main() {
19926            println!("hello");
19927
19928            println!("world");
19929        }"#
19930        .unindent(),
19931    );
19932
19933    cx.set_head_text(&diff_base);
19934    executor.run_until_parked();
19935    cx.update_editor(|editor, window, cx| {
19936        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19937    });
19938    executor.run_until_parked();
19939
19940    cx.assert_state_with_diff(
19941        r#"
19942        use some::mod1;
19943        use some::mod2;
19944
19945        const A: u32 = 42;
19946        const B: u32 = 42;
19947      - const C: u32 = 42;
19948      + const C: u32 = 43ˇ
19949        const D: u32 = 42;
19950
19951
19952        fn main() {
19953            println!("hello");
19954
19955            println!("world");
19956        }"#
19957        .unindent(),
19958    );
19959
19960    cx.update_editor(|editor, window, cx| {
19961        editor.handle_input("\nnew_line\n", window, cx);
19962    });
19963    executor.run_until_parked();
19964
19965    cx.assert_state_with_diff(
19966        r#"
19967        use some::mod1;
19968        use some::mod2;
19969
19970        const A: u32 = 42;
19971        const B: u32 = 42;
19972      - const C: u32 = 42;
19973      + const C: u32 = 43
19974      + new_line
19975      + ˇ
19976        const D: u32 = 42;
19977
19978
19979        fn main() {
19980            println!("hello");
19981
19982            println!("world");
19983        }"#
19984        .unindent(),
19985    );
19986}
19987
19988#[gpui::test]
19989async fn test_stage_and_unstage_added_file_hunk(
19990    executor: BackgroundExecutor,
19991    cx: &mut TestAppContext,
19992) {
19993    init_test(cx, |_| {});
19994
19995    let mut cx = EditorTestContext::new(cx).await;
19996    cx.update_editor(|editor, _, cx| {
19997        editor.set_expand_all_diff_hunks(cx);
19998    });
19999
20000    let working_copy = r#"
20001            ˇfn main() {
20002                println!("hello, world!");
20003            }
20004        "#
20005    .unindent();
20006
20007    cx.set_state(&working_copy);
20008    executor.run_until_parked();
20009
20010    cx.assert_state_with_diff(
20011        r#"
20012            + ˇfn main() {
20013            +     println!("hello, world!");
20014            + }
20015        "#
20016        .unindent(),
20017    );
20018    cx.assert_index_text(None);
20019
20020    cx.update_editor(|editor, window, cx| {
20021        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20022    });
20023    executor.run_until_parked();
20024    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20025    cx.assert_state_with_diff(
20026        r#"
20027            + ˇfn main() {
20028            +     println!("hello, world!");
20029            + }
20030        "#
20031        .unindent(),
20032    );
20033
20034    cx.update_editor(|editor, window, cx| {
20035        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20036    });
20037    executor.run_until_parked();
20038    cx.assert_index_text(None);
20039}
20040
20041async fn setup_indent_guides_editor(
20042    text: &str,
20043    cx: &mut TestAppContext,
20044) -> (BufferId, EditorTestContext) {
20045    init_test(cx, |_| {});
20046
20047    let mut cx = EditorTestContext::new(cx).await;
20048
20049    let buffer_id = cx.update_editor(|editor, window, cx| {
20050        editor.set_text(text, window, cx);
20051        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20052
20053        buffer_ids[0]
20054    });
20055
20056    (buffer_id, cx)
20057}
20058
20059fn assert_indent_guides(
20060    range: Range<u32>,
20061    expected: Vec<IndentGuide>,
20062    active_indices: Option<Vec<usize>>,
20063    cx: &mut EditorTestContext,
20064) {
20065    let indent_guides = cx.update_editor(|editor, window, cx| {
20066        let snapshot = editor.snapshot(window, cx).display_snapshot;
20067        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20068            editor,
20069            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20070            true,
20071            &snapshot,
20072            cx,
20073        );
20074
20075        indent_guides.sort_by(|a, b| {
20076            a.depth.cmp(&b.depth).then(
20077                a.start_row
20078                    .cmp(&b.start_row)
20079                    .then(a.end_row.cmp(&b.end_row)),
20080            )
20081        });
20082        indent_guides
20083    });
20084
20085    if let Some(expected) = active_indices {
20086        let active_indices = cx.update_editor(|editor, window, cx| {
20087            let snapshot = editor.snapshot(window, cx).display_snapshot;
20088            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20089        });
20090
20091        assert_eq!(
20092            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20093            expected,
20094            "Active indent guide indices do not match"
20095        );
20096    }
20097
20098    assert_eq!(indent_guides, expected, "Indent guides do not match");
20099}
20100
20101fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20102    IndentGuide {
20103        buffer_id,
20104        start_row: MultiBufferRow(start_row),
20105        end_row: MultiBufferRow(end_row),
20106        depth,
20107        tab_size: 4,
20108        settings: IndentGuideSettings {
20109            enabled: true,
20110            line_width: 1,
20111            active_line_width: 1,
20112            coloring: IndentGuideColoring::default(),
20113            background_coloring: IndentGuideBackgroundColoring::default(),
20114        },
20115    }
20116}
20117
20118#[gpui::test]
20119async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20120    let (buffer_id, mut cx) = setup_indent_guides_editor(
20121        &"
20122        fn main() {
20123            let a = 1;
20124        }"
20125        .unindent(),
20126        cx,
20127    )
20128    .await;
20129
20130    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20131}
20132
20133#[gpui::test]
20134async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20135    let (buffer_id, mut cx) = setup_indent_guides_editor(
20136        &"
20137        fn main() {
20138            let a = 1;
20139            let b = 2;
20140        }"
20141        .unindent(),
20142        cx,
20143    )
20144    .await;
20145
20146    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20147}
20148
20149#[gpui::test]
20150async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20151    let (buffer_id, mut cx) = setup_indent_guides_editor(
20152        &"
20153        fn main() {
20154            let a = 1;
20155            if a == 3 {
20156                let b = 2;
20157            } else {
20158                let c = 3;
20159            }
20160        }"
20161        .unindent(),
20162        cx,
20163    )
20164    .await;
20165
20166    assert_indent_guides(
20167        0..8,
20168        vec![
20169            indent_guide(buffer_id, 1, 6, 0),
20170            indent_guide(buffer_id, 3, 3, 1),
20171            indent_guide(buffer_id, 5, 5, 1),
20172        ],
20173        None,
20174        &mut cx,
20175    );
20176}
20177
20178#[gpui::test]
20179async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20180    let (buffer_id, mut cx) = setup_indent_guides_editor(
20181        &"
20182        fn main() {
20183            let a = 1;
20184                let b = 2;
20185            let c = 3;
20186        }"
20187        .unindent(),
20188        cx,
20189    )
20190    .await;
20191
20192    assert_indent_guides(
20193        0..5,
20194        vec![
20195            indent_guide(buffer_id, 1, 3, 0),
20196            indent_guide(buffer_id, 2, 2, 1),
20197        ],
20198        None,
20199        &mut cx,
20200    );
20201}
20202
20203#[gpui::test]
20204async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20205    let (buffer_id, mut cx) = setup_indent_guides_editor(
20206        &"
20207        fn main() {
20208            let a = 1;
20209
20210            let c = 3;
20211        }"
20212        .unindent(),
20213        cx,
20214    )
20215    .await;
20216
20217    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20218}
20219
20220#[gpui::test]
20221async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20222    let (buffer_id, mut cx) = setup_indent_guides_editor(
20223        &"
20224        fn main() {
20225            let a = 1;
20226
20227            let c = 3;
20228
20229            if a == 3 {
20230                let b = 2;
20231            } else {
20232                let c = 3;
20233            }
20234        }"
20235        .unindent(),
20236        cx,
20237    )
20238    .await;
20239
20240    assert_indent_guides(
20241        0..11,
20242        vec![
20243            indent_guide(buffer_id, 1, 9, 0),
20244            indent_guide(buffer_id, 6, 6, 1),
20245            indent_guide(buffer_id, 8, 8, 1),
20246        ],
20247        None,
20248        &mut cx,
20249    );
20250}
20251
20252#[gpui::test]
20253async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20254    let (buffer_id, mut cx) = setup_indent_guides_editor(
20255        &"
20256        fn main() {
20257            let a = 1;
20258
20259            let c = 3;
20260
20261            if a == 3 {
20262                let b = 2;
20263            } else {
20264                let c = 3;
20265            }
20266        }"
20267        .unindent(),
20268        cx,
20269    )
20270    .await;
20271
20272    assert_indent_guides(
20273        1..11,
20274        vec![
20275            indent_guide(buffer_id, 1, 9, 0),
20276            indent_guide(buffer_id, 6, 6, 1),
20277            indent_guide(buffer_id, 8, 8, 1),
20278        ],
20279        None,
20280        &mut cx,
20281    );
20282}
20283
20284#[gpui::test]
20285async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20286    let (buffer_id, mut cx) = setup_indent_guides_editor(
20287        &"
20288        fn main() {
20289            let a = 1;
20290
20291            let c = 3;
20292
20293            if a == 3 {
20294                let b = 2;
20295            } else {
20296                let c = 3;
20297            }
20298        }"
20299        .unindent(),
20300        cx,
20301    )
20302    .await;
20303
20304    assert_indent_guides(
20305        1..10,
20306        vec![
20307            indent_guide(buffer_id, 1, 9, 0),
20308            indent_guide(buffer_id, 6, 6, 1),
20309            indent_guide(buffer_id, 8, 8, 1),
20310        ],
20311        None,
20312        &mut cx,
20313    );
20314}
20315
20316#[gpui::test]
20317async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20318    let (buffer_id, mut cx) = setup_indent_guides_editor(
20319        &"
20320        fn main() {
20321            if a {
20322                b(
20323                    c,
20324                    d,
20325                )
20326            } else {
20327                e(
20328                    f
20329                )
20330            }
20331        }"
20332        .unindent(),
20333        cx,
20334    )
20335    .await;
20336
20337    assert_indent_guides(
20338        0..11,
20339        vec![
20340            indent_guide(buffer_id, 1, 10, 0),
20341            indent_guide(buffer_id, 2, 5, 1),
20342            indent_guide(buffer_id, 7, 9, 1),
20343            indent_guide(buffer_id, 3, 4, 2),
20344            indent_guide(buffer_id, 8, 8, 2),
20345        ],
20346        None,
20347        &mut cx,
20348    );
20349
20350    cx.update_editor(|editor, window, cx| {
20351        editor.fold_at(MultiBufferRow(2), window, cx);
20352        assert_eq!(
20353            editor.display_text(cx),
20354            "
20355            fn main() {
20356                if a {
20357                    b(⋯
20358                    )
20359                } else {
20360                    e(
20361                        f
20362                    )
20363                }
20364            }"
20365            .unindent()
20366        );
20367    });
20368
20369    assert_indent_guides(
20370        0..11,
20371        vec![
20372            indent_guide(buffer_id, 1, 10, 0),
20373            indent_guide(buffer_id, 2, 5, 1),
20374            indent_guide(buffer_id, 7, 9, 1),
20375            indent_guide(buffer_id, 8, 8, 2),
20376        ],
20377        None,
20378        &mut cx,
20379    );
20380}
20381
20382#[gpui::test]
20383async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20384    let (buffer_id, mut cx) = setup_indent_guides_editor(
20385        &"
20386        block1
20387            block2
20388                block3
20389                    block4
20390            block2
20391        block1
20392        block1"
20393            .unindent(),
20394        cx,
20395    )
20396    .await;
20397
20398    assert_indent_guides(
20399        1..10,
20400        vec![
20401            indent_guide(buffer_id, 1, 4, 0),
20402            indent_guide(buffer_id, 2, 3, 1),
20403            indent_guide(buffer_id, 3, 3, 2),
20404        ],
20405        None,
20406        &mut cx,
20407    );
20408}
20409
20410#[gpui::test]
20411async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20412    let (buffer_id, mut cx) = setup_indent_guides_editor(
20413        &"
20414        block1
20415            block2
20416                block3
20417
20418        block1
20419        block1"
20420            .unindent(),
20421        cx,
20422    )
20423    .await;
20424
20425    assert_indent_guides(
20426        0..6,
20427        vec![
20428            indent_guide(buffer_id, 1, 2, 0),
20429            indent_guide(buffer_id, 2, 2, 1),
20430        ],
20431        None,
20432        &mut cx,
20433    );
20434}
20435
20436#[gpui::test]
20437async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20438    let (buffer_id, mut cx) = setup_indent_guides_editor(
20439        &"
20440        function component() {
20441        \treturn (
20442        \t\t\t
20443        \t\t<div>
20444        \t\t\t<abc></abc>
20445        \t\t</div>
20446        \t)
20447        }"
20448        .unindent(),
20449        cx,
20450    )
20451    .await;
20452
20453    assert_indent_guides(
20454        0..8,
20455        vec![
20456            indent_guide(buffer_id, 1, 6, 0),
20457            indent_guide(buffer_id, 2, 5, 1),
20458            indent_guide(buffer_id, 4, 4, 2),
20459        ],
20460        None,
20461        &mut cx,
20462    );
20463}
20464
20465#[gpui::test]
20466async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20467    let (buffer_id, mut cx) = setup_indent_guides_editor(
20468        &"
20469        function component() {
20470        \treturn (
20471        \t
20472        \t\t<div>
20473        \t\t\t<abc></abc>
20474        \t\t</div>
20475        \t)
20476        }"
20477        .unindent(),
20478        cx,
20479    )
20480    .await;
20481
20482    assert_indent_guides(
20483        0..8,
20484        vec![
20485            indent_guide(buffer_id, 1, 6, 0),
20486            indent_guide(buffer_id, 2, 5, 1),
20487            indent_guide(buffer_id, 4, 4, 2),
20488        ],
20489        None,
20490        &mut cx,
20491    );
20492}
20493
20494#[gpui::test]
20495async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20496    let (buffer_id, mut cx) = setup_indent_guides_editor(
20497        &"
20498        block1
20499
20500
20501
20502            block2
20503        "
20504        .unindent(),
20505        cx,
20506    )
20507    .await;
20508
20509    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20510}
20511
20512#[gpui::test]
20513async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20514    let (buffer_id, mut cx) = setup_indent_guides_editor(
20515        &"
20516        def a:
20517        \tb = 3
20518        \tif True:
20519        \t\tc = 4
20520        \t\td = 5
20521        \tprint(b)
20522        "
20523        .unindent(),
20524        cx,
20525    )
20526    .await;
20527
20528    assert_indent_guides(
20529        0..6,
20530        vec![
20531            indent_guide(buffer_id, 1, 5, 0),
20532            indent_guide(buffer_id, 3, 4, 1),
20533        ],
20534        None,
20535        &mut cx,
20536    );
20537}
20538
20539#[gpui::test]
20540async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20541    let (buffer_id, mut cx) = setup_indent_guides_editor(
20542        &"
20543    fn main() {
20544        let a = 1;
20545    }"
20546        .unindent(),
20547        cx,
20548    )
20549    .await;
20550
20551    cx.update_editor(|editor, window, cx| {
20552        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20553            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20554        });
20555    });
20556
20557    assert_indent_guides(
20558        0..3,
20559        vec![indent_guide(buffer_id, 1, 1, 0)],
20560        Some(vec![0]),
20561        &mut cx,
20562    );
20563}
20564
20565#[gpui::test]
20566async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20567    let (buffer_id, mut cx) = setup_indent_guides_editor(
20568        &"
20569    fn main() {
20570        if 1 == 2 {
20571            let a = 1;
20572        }
20573    }"
20574        .unindent(),
20575        cx,
20576    )
20577    .await;
20578
20579    cx.update_editor(|editor, window, cx| {
20580        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20581            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20582        });
20583    });
20584
20585    assert_indent_guides(
20586        0..4,
20587        vec![
20588            indent_guide(buffer_id, 1, 3, 0),
20589            indent_guide(buffer_id, 2, 2, 1),
20590        ],
20591        Some(vec![1]),
20592        &mut cx,
20593    );
20594
20595    cx.update_editor(|editor, window, cx| {
20596        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20597            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20598        });
20599    });
20600
20601    assert_indent_guides(
20602        0..4,
20603        vec![
20604            indent_guide(buffer_id, 1, 3, 0),
20605            indent_guide(buffer_id, 2, 2, 1),
20606        ],
20607        Some(vec![1]),
20608        &mut cx,
20609    );
20610
20611    cx.update_editor(|editor, window, cx| {
20612        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20613            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20614        });
20615    });
20616
20617    assert_indent_guides(
20618        0..4,
20619        vec![
20620            indent_guide(buffer_id, 1, 3, 0),
20621            indent_guide(buffer_id, 2, 2, 1),
20622        ],
20623        Some(vec![0]),
20624        &mut cx,
20625    );
20626}
20627
20628#[gpui::test]
20629async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20630    let (buffer_id, mut cx) = setup_indent_guides_editor(
20631        &"
20632    fn main() {
20633        let a = 1;
20634
20635        let b = 2;
20636    }"
20637        .unindent(),
20638        cx,
20639    )
20640    .await;
20641
20642    cx.update_editor(|editor, window, cx| {
20643        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20644            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20645        });
20646    });
20647
20648    assert_indent_guides(
20649        0..5,
20650        vec![indent_guide(buffer_id, 1, 3, 0)],
20651        Some(vec![0]),
20652        &mut cx,
20653    );
20654}
20655
20656#[gpui::test]
20657async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20658    let (buffer_id, mut cx) = setup_indent_guides_editor(
20659        &"
20660    def m:
20661        a = 1
20662        pass"
20663            .unindent(),
20664        cx,
20665    )
20666    .await;
20667
20668    cx.update_editor(|editor, window, cx| {
20669        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20670            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20671        });
20672    });
20673
20674    assert_indent_guides(
20675        0..3,
20676        vec![indent_guide(buffer_id, 1, 2, 0)],
20677        Some(vec![0]),
20678        &mut cx,
20679    );
20680}
20681
20682#[gpui::test]
20683async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20684    init_test(cx, |_| {});
20685    let mut cx = EditorTestContext::new(cx).await;
20686    let text = indoc! {
20687        "
20688        impl A {
20689            fn b() {
20690                0;
20691                3;
20692                5;
20693                6;
20694                7;
20695            }
20696        }
20697        "
20698    };
20699    let base_text = indoc! {
20700        "
20701        impl A {
20702            fn b() {
20703                0;
20704                1;
20705                2;
20706                3;
20707                4;
20708            }
20709            fn c() {
20710                5;
20711                6;
20712                7;
20713            }
20714        }
20715        "
20716    };
20717
20718    cx.update_editor(|editor, window, cx| {
20719        editor.set_text(text, window, cx);
20720
20721        editor.buffer().update(cx, |multibuffer, cx| {
20722            let buffer = multibuffer.as_singleton().unwrap();
20723            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20724
20725            multibuffer.set_all_diff_hunks_expanded(cx);
20726            multibuffer.add_diff(diff, cx);
20727
20728            buffer.read(cx).remote_id()
20729        })
20730    });
20731    cx.run_until_parked();
20732
20733    cx.assert_state_with_diff(
20734        indoc! { "
20735          impl A {
20736              fn b() {
20737                  0;
20738        -         1;
20739        -         2;
20740                  3;
20741        -         4;
20742        -     }
20743        -     fn c() {
20744                  5;
20745                  6;
20746                  7;
20747              }
20748          }
20749          ˇ"
20750        }
20751        .to_string(),
20752    );
20753
20754    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20755        editor
20756            .snapshot(window, cx)
20757            .buffer_snapshot()
20758            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20759            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20760            .collect::<Vec<_>>()
20761    });
20762    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20763    assert_eq!(
20764        actual_guides,
20765        vec![
20766            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20767            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20768            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20769        ]
20770    );
20771}
20772
20773#[gpui::test]
20774async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20775    init_test(cx, |_| {});
20776    let mut cx = EditorTestContext::new(cx).await;
20777
20778    let diff_base = r#"
20779        a
20780        b
20781        c
20782        "#
20783    .unindent();
20784
20785    cx.set_state(
20786        &r#"
20787        ˇA
20788        b
20789        C
20790        "#
20791        .unindent(),
20792    );
20793    cx.set_head_text(&diff_base);
20794    cx.update_editor(|editor, window, cx| {
20795        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20796    });
20797    executor.run_until_parked();
20798
20799    let both_hunks_expanded = r#"
20800        - a
20801        + ˇA
20802          b
20803        - c
20804        + C
20805        "#
20806    .unindent();
20807
20808    cx.assert_state_with_diff(both_hunks_expanded.clone());
20809
20810    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20811        let snapshot = editor.snapshot(window, cx);
20812        let hunks = editor
20813            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
20814            .collect::<Vec<_>>();
20815        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20816        let buffer_id = hunks[0].buffer_id;
20817        hunks
20818            .into_iter()
20819            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20820            .collect::<Vec<_>>()
20821    });
20822    assert_eq!(hunk_ranges.len(), 2);
20823
20824    cx.update_editor(|editor, _, cx| {
20825        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20826    });
20827    executor.run_until_parked();
20828
20829    let second_hunk_expanded = r#"
20830          ˇA
20831          b
20832        - c
20833        + C
20834        "#
20835    .unindent();
20836
20837    cx.assert_state_with_diff(second_hunk_expanded);
20838
20839    cx.update_editor(|editor, _, cx| {
20840        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20841    });
20842    executor.run_until_parked();
20843
20844    cx.assert_state_with_diff(both_hunks_expanded.clone());
20845
20846    cx.update_editor(|editor, _, cx| {
20847        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20848    });
20849    executor.run_until_parked();
20850
20851    let first_hunk_expanded = r#"
20852        - a
20853        + ˇA
20854          b
20855          C
20856        "#
20857    .unindent();
20858
20859    cx.assert_state_with_diff(first_hunk_expanded);
20860
20861    cx.update_editor(|editor, _, cx| {
20862        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20863    });
20864    executor.run_until_parked();
20865
20866    cx.assert_state_with_diff(both_hunks_expanded);
20867
20868    cx.set_state(
20869        &r#"
20870        ˇA
20871        b
20872        "#
20873        .unindent(),
20874    );
20875    cx.run_until_parked();
20876
20877    // TODO this cursor position seems bad
20878    cx.assert_state_with_diff(
20879        r#"
20880        - ˇa
20881        + A
20882          b
20883        "#
20884        .unindent(),
20885    );
20886
20887    cx.update_editor(|editor, window, cx| {
20888        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20889    });
20890
20891    cx.assert_state_with_diff(
20892        r#"
20893            - ˇa
20894            + A
20895              b
20896            - c
20897            "#
20898        .unindent(),
20899    );
20900
20901    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20902        let snapshot = editor.snapshot(window, cx);
20903        let hunks = editor
20904            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
20905            .collect::<Vec<_>>();
20906        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20907        let buffer_id = hunks[0].buffer_id;
20908        hunks
20909            .into_iter()
20910            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20911            .collect::<Vec<_>>()
20912    });
20913    assert_eq!(hunk_ranges.len(), 2);
20914
20915    cx.update_editor(|editor, _, cx| {
20916        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20917    });
20918    executor.run_until_parked();
20919
20920    cx.assert_state_with_diff(
20921        r#"
20922        - ˇa
20923        + A
20924          b
20925        "#
20926        .unindent(),
20927    );
20928}
20929
20930#[gpui::test]
20931async fn test_toggle_deletion_hunk_at_start_of_file(
20932    executor: BackgroundExecutor,
20933    cx: &mut TestAppContext,
20934) {
20935    init_test(cx, |_| {});
20936    let mut cx = EditorTestContext::new(cx).await;
20937
20938    let diff_base = r#"
20939        a
20940        b
20941        c
20942        "#
20943    .unindent();
20944
20945    cx.set_state(
20946        &r#"
20947        ˇb
20948        c
20949        "#
20950        .unindent(),
20951    );
20952    cx.set_head_text(&diff_base);
20953    cx.update_editor(|editor, window, cx| {
20954        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20955    });
20956    executor.run_until_parked();
20957
20958    let hunk_expanded = r#"
20959        - a
20960          ˇb
20961          c
20962        "#
20963    .unindent();
20964
20965    cx.assert_state_with_diff(hunk_expanded.clone());
20966
20967    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20968        let snapshot = editor.snapshot(window, cx);
20969        let hunks = editor
20970            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
20971            .collect::<Vec<_>>();
20972        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20973        let buffer_id = hunks[0].buffer_id;
20974        hunks
20975            .into_iter()
20976            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20977            .collect::<Vec<_>>()
20978    });
20979    assert_eq!(hunk_ranges.len(), 1);
20980
20981    cx.update_editor(|editor, _, cx| {
20982        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20983    });
20984    executor.run_until_parked();
20985
20986    let hunk_collapsed = r#"
20987          ˇb
20988          c
20989        "#
20990    .unindent();
20991
20992    cx.assert_state_with_diff(hunk_collapsed);
20993
20994    cx.update_editor(|editor, _, cx| {
20995        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20996    });
20997    executor.run_until_parked();
20998
20999    cx.assert_state_with_diff(hunk_expanded);
21000}
21001
21002#[gpui::test]
21003async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21004    init_test(cx, |_| {});
21005
21006    let fs = FakeFs::new(cx.executor());
21007    fs.insert_tree(
21008        path!("/test"),
21009        json!({
21010            ".git": {},
21011            "file-1": "ONE\n",
21012            "file-2": "TWO\n",
21013            "file-3": "THREE\n",
21014        }),
21015    )
21016    .await;
21017
21018    fs.set_head_for_repo(
21019        path!("/test/.git").as_ref(),
21020        &[
21021            ("file-1", "one\n".into()),
21022            ("file-2", "two\n".into()),
21023            ("file-3", "three\n".into()),
21024        ],
21025        "deadbeef",
21026    );
21027
21028    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21029    let mut buffers = vec![];
21030    for i in 1..=3 {
21031        let buffer = project
21032            .update(cx, |project, cx| {
21033                let path = format!(path!("/test/file-{}"), i);
21034                project.open_local_buffer(path, cx)
21035            })
21036            .await
21037            .unwrap();
21038        buffers.push(buffer);
21039    }
21040
21041    let multibuffer = cx.new(|cx| {
21042        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21043        multibuffer.set_all_diff_hunks_expanded(cx);
21044        for buffer in &buffers {
21045            let snapshot = buffer.read(cx).snapshot();
21046            multibuffer.set_excerpts_for_path(
21047                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
21048                buffer.clone(),
21049                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21050                2,
21051                cx,
21052            );
21053        }
21054        multibuffer
21055    });
21056
21057    let editor = cx.add_window(|window, cx| {
21058        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21059    });
21060    cx.run_until_parked();
21061
21062    let snapshot = editor
21063        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21064        .unwrap();
21065    let hunks = snapshot
21066        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21067        .map(|hunk| match hunk {
21068            DisplayDiffHunk::Unfolded {
21069                display_row_range, ..
21070            } => display_row_range,
21071            DisplayDiffHunk::Folded { .. } => unreachable!(),
21072        })
21073        .collect::<Vec<_>>();
21074    assert_eq!(
21075        hunks,
21076        [
21077            DisplayRow(2)..DisplayRow(4),
21078            DisplayRow(7)..DisplayRow(9),
21079            DisplayRow(12)..DisplayRow(14),
21080        ]
21081    );
21082}
21083
21084#[gpui::test]
21085async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21086    init_test(cx, |_| {});
21087
21088    let mut cx = EditorTestContext::new(cx).await;
21089    cx.set_head_text(indoc! { "
21090        one
21091        two
21092        three
21093        four
21094        five
21095        "
21096    });
21097    cx.set_index_text(indoc! { "
21098        one
21099        two
21100        three
21101        four
21102        five
21103        "
21104    });
21105    cx.set_state(indoc! {"
21106        one
21107        TWO
21108        ˇTHREE
21109        FOUR
21110        five
21111    "});
21112    cx.run_until_parked();
21113    cx.update_editor(|editor, window, cx| {
21114        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21115    });
21116    cx.run_until_parked();
21117    cx.assert_index_text(Some(indoc! {"
21118        one
21119        TWO
21120        THREE
21121        FOUR
21122        five
21123    "}));
21124    cx.set_state(indoc! { "
21125        one
21126        TWO
21127        ˇTHREE-HUNDRED
21128        FOUR
21129        five
21130    "});
21131    cx.run_until_parked();
21132    cx.update_editor(|editor, window, cx| {
21133        let snapshot = editor.snapshot(window, cx);
21134        let hunks = editor
21135            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21136            .collect::<Vec<_>>();
21137        assert_eq!(hunks.len(), 1);
21138        assert_eq!(
21139            hunks[0].status(),
21140            DiffHunkStatus {
21141                kind: DiffHunkStatusKind::Modified,
21142                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21143            }
21144        );
21145
21146        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21147    });
21148    cx.run_until_parked();
21149    cx.assert_index_text(Some(indoc! {"
21150        one
21151        TWO
21152        THREE-HUNDRED
21153        FOUR
21154        five
21155    "}));
21156}
21157
21158#[gpui::test]
21159fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21160    init_test(cx, |_| {});
21161
21162    let editor = cx.add_window(|window, cx| {
21163        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21164        build_editor(buffer, window, cx)
21165    });
21166
21167    let render_args = Arc::new(Mutex::new(None));
21168    let snapshot = editor
21169        .update(cx, |editor, window, cx| {
21170            let snapshot = editor.buffer().read(cx).snapshot(cx);
21171            let range =
21172                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21173
21174            struct RenderArgs {
21175                row: MultiBufferRow,
21176                folded: bool,
21177                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21178            }
21179
21180            let crease = Crease::inline(
21181                range,
21182                FoldPlaceholder::test(),
21183                {
21184                    let toggle_callback = render_args.clone();
21185                    move |row, folded, callback, _window, _cx| {
21186                        *toggle_callback.lock() = Some(RenderArgs {
21187                            row,
21188                            folded,
21189                            callback,
21190                        });
21191                        div()
21192                    }
21193                },
21194                |_row, _folded, _window, _cx| div(),
21195            );
21196
21197            editor.insert_creases(Some(crease), cx);
21198            let snapshot = editor.snapshot(window, cx);
21199            let _div =
21200                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21201            snapshot
21202        })
21203        .unwrap();
21204
21205    let render_args = render_args.lock().take().unwrap();
21206    assert_eq!(render_args.row, MultiBufferRow(1));
21207    assert!(!render_args.folded);
21208    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21209
21210    cx.update_window(*editor, |_, window, cx| {
21211        (render_args.callback)(true, window, cx)
21212    })
21213    .unwrap();
21214    let snapshot = editor
21215        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21216        .unwrap();
21217    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21218
21219    cx.update_window(*editor, |_, window, cx| {
21220        (render_args.callback)(false, window, cx)
21221    })
21222    .unwrap();
21223    let snapshot = editor
21224        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21225        .unwrap();
21226    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21227}
21228
21229#[gpui::test]
21230async fn test_input_text(cx: &mut TestAppContext) {
21231    init_test(cx, |_| {});
21232    let mut cx = EditorTestContext::new(cx).await;
21233
21234    cx.set_state(
21235        &r#"ˇone
21236        two
21237
21238        three
21239        fourˇ
21240        five
21241
21242        siˇx"#
21243            .unindent(),
21244    );
21245
21246    cx.dispatch_action(HandleInput(String::new()));
21247    cx.assert_editor_state(
21248        &r#"ˇone
21249        two
21250
21251        three
21252        fourˇ
21253        five
21254
21255        siˇx"#
21256            .unindent(),
21257    );
21258
21259    cx.dispatch_action(HandleInput("AAAA".to_string()));
21260    cx.assert_editor_state(
21261        &r#"AAAAˇone
21262        two
21263
21264        three
21265        fourAAAAˇ
21266        five
21267
21268        siAAAAˇx"#
21269            .unindent(),
21270    );
21271}
21272
21273#[gpui::test]
21274async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21275    init_test(cx, |_| {});
21276
21277    let mut cx = EditorTestContext::new(cx).await;
21278    cx.set_state(
21279        r#"let foo = 1;
21280let foo = 2;
21281let foo = 3;
21282let fooˇ = 4;
21283let foo = 5;
21284let foo = 6;
21285let foo = 7;
21286let foo = 8;
21287let foo = 9;
21288let foo = 10;
21289let foo = 11;
21290let foo = 12;
21291let foo = 13;
21292let foo = 14;
21293let foo = 15;"#,
21294    );
21295
21296    cx.update_editor(|e, window, cx| {
21297        assert_eq!(
21298            e.next_scroll_position,
21299            NextScrollCursorCenterTopBottom::Center,
21300            "Default next scroll direction is center",
21301        );
21302
21303        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21304        assert_eq!(
21305            e.next_scroll_position,
21306            NextScrollCursorCenterTopBottom::Top,
21307            "After center, next scroll direction should be top",
21308        );
21309
21310        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21311        assert_eq!(
21312            e.next_scroll_position,
21313            NextScrollCursorCenterTopBottom::Bottom,
21314            "After top, next scroll direction should be bottom",
21315        );
21316
21317        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21318        assert_eq!(
21319            e.next_scroll_position,
21320            NextScrollCursorCenterTopBottom::Center,
21321            "After bottom, scrolling should start over",
21322        );
21323
21324        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21325        assert_eq!(
21326            e.next_scroll_position,
21327            NextScrollCursorCenterTopBottom::Top,
21328            "Scrolling continues if retriggered fast enough"
21329        );
21330    });
21331
21332    cx.executor()
21333        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21334    cx.executor().run_until_parked();
21335    cx.update_editor(|e, _, _| {
21336        assert_eq!(
21337            e.next_scroll_position,
21338            NextScrollCursorCenterTopBottom::Center,
21339            "If scrolling is not triggered fast enough, it should reset"
21340        );
21341    });
21342}
21343
21344#[gpui::test]
21345async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21346    init_test(cx, |_| {});
21347    let mut cx = EditorLspTestContext::new_rust(
21348        lsp::ServerCapabilities {
21349            definition_provider: Some(lsp::OneOf::Left(true)),
21350            references_provider: Some(lsp::OneOf::Left(true)),
21351            ..lsp::ServerCapabilities::default()
21352        },
21353        cx,
21354    )
21355    .await;
21356
21357    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21358        let go_to_definition = cx
21359            .lsp
21360            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21361                move |params, _| async move {
21362                    if empty_go_to_definition {
21363                        Ok(None)
21364                    } else {
21365                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21366                            uri: params.text_document_position_params.text_document.uri,
21367                            range: lsp::Range::new(
21368                                lsp::Position::new(4, 3),
21369                                lsp::Position::new(4, 6),
21370                            ),
21371                        })))
21372                    }
21373                },
21374            );
21375        let references = cx
21376            .lsp
21377            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21378                Ok(Some(vec![lsp::Location {
21379                    uri: params.text_document_position.text_document.uri,
21380                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21381                }]))
21382            });
21383        (go_to_definition, references)
21384    };
21385
21386    cx.set_state(
21387        &r#"fn one() {
21388            let mut a = ˇtwo();
21389        }
21390
21391        fn two() {}"#
21392            .unindent(),
21393    );
21394    set_up_lsp_handlers(false, &mut cx);
21395    let navigated = cx
21396        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21397        .await
21398        .expect("Failed to navigate to definition");
21399    assert_eq!(
21400        navigated,
21401        Navigated::Yes,
21402        "Should have navigated to definition from the GetDefinition response"
21403    );
21404    cx.assert_editor_state(
21405        &r#"fn one() {
21406            let mut a = two();
21407        }
21408
21409        fn «twoˇ»() {}"#
21410            .unindent(),
21411    );
21412
21413    let editors = cx.update_workspace(|workspace, _, cx| {
21414        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21415    });
21416    cx.update_editor(|_, _, test_editor_cx| {
21417        assert_eq!(
21418            editors.len(),
21419            1,
21420            "Initially, only one, test, editor should be open in the workspace"
21421        );
21422        assert_eq!(
21423            test_editor_cx.entity(),
21424            editors.last().expect("Asserted len is 1").clone()
21425        );
21426    });
21427
21428    set_up_lsp_handlers(true, &mut cx);
21429    let navigated = cx
21430        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21431        .await
21432        .expect("Failed to navigate to lookup references");
21433    assert_eq!(
21434        navigated,
21435        Navigated::Yes,
21436        "Should have navigated to references as a fallback after empty GoToDefinition response"
21437    );
21438    // We should not change the selections in the existing file,
21439    // if opening another milti buffer with the references
21440    cx.assert_editor_state(
21441        &r#"fn one() {
21442            let mut a = two();
21443        }
21444
21445        fn «twoˇ»() {}"#
21446            .unindent(),
21447    );
21448    let editors = cx.update_workspace(|workspace, _, cx| {
21449        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21450    });
21451    cx.update_editor(|_, _, test_editor_cx| {
21452        assert_eq!(
21453            editors.len(),
21454            2,
21455            "After falling back to references search, we open a new editor with the results"
21456        );
21457        let references_fallback_text = editors
21458            .into_iter()
21459            .find(|new_editor| *new_editor != test_editor_cx.entity())
21460            .expect("Should have one non-test editor now")
21461            .read(test_editor_cx)
21462            .text(test_editor_cx);
21463        assert_eq!(
21464            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21465            "Should use the range from the references response and not the GoToDefinition one"
21466        );
21467    });
21468}
21469
21470#[gpui::test]
21471async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21472    init_test(cx, |_| {});
21473    cx.update(|cx| {
21474        let mut editor_settings = EditorSettings::get_global(cx).clone();
21475        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21476        EditorSettings::override_global(editor_settings, cx);
21477    });
21478    let mut cx = EditorLspTestContext::new_rust(
21479        lsp::ServerCapabilities {
21480            definition_provider: Some(lsp::OneOf::Left(true)),
21481            references_provider: Some(lsp::OneOf::Left(true)),
21482            ..lsp::ServerCapabilities::default()
21483        },
21484        cx,
21485    )
21486    .await;
21487    let original_state = r#"fn one() {
21488        let mut a = ˇtwo();
21489    }
21490
21491    fn two() {}"#
21492        .unindent();
21493    cx.set_state(&original_state);
21494
21495    let mut go_to_definition = cx
21496        .lsp
21497        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21498            move |_, _| async move { Ok(None) },
21499        );
21500    let _references = cx
21501        .lsp
21502        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21503            panic!("Should not call for references with no go to definition fallback")
21504        });
21505
21506    let navigated = cx
21507        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21508        .await
21509        .expect("Failed to navigate to lookup references");
21510    go_to_definition
21511        .next()
21512        .await
21513        .expect("Should have called the go_to_definition handler");
21514
21515    assert_eq!(
21516        navigated,
21517        Navigated::No,
21518        "Should have navigated to references as a fallback after empty GoToDefinition response"
21519    );
21520    cx.assert_editor_state(&original_state);
21521    let editors = cx.update_workspace(|workspace, _, cx| {
21522        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21523    });
21524    cx.update_editor(|_, _, _| {
21525        assert_eq!(
21526            editors.len(),
21527            1,
21528            "After unsuccessful fallback, no other editor should have been opened"
21529        );
21530    });
21531}
21532
21533#[gpui::test]
21534async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21535    init_test(cx, |_| {});
21536    let mut cx = EditorLspTestContext::new_rust(
21537        lsp::ServerCapabilities {
21538            references_provider: Some(lsp::OneOf::Left(true)),
21539            ..lsp::ServerCapabilities::default()
21540        },
21541        cx,
21542    )
21543    .await;
21544
21545    cx.set_state(
21546        &r#"
21547        fn one() {
21548            let mut a = two();
21549        }
21550
21551        fn ˇtwo() {}"#
21552            .unindent(),
21553    );
21554    cx.lsp
21555        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21556            Ok(Some(vec![
21557                lsp::Location {
21558                    uri: params.text_document_position.text_document.uri.clone(),
21559                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21560                },
21561                lsp::Location {
21562                    uri: params.text_document_position.text_document.uri,
21563                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21564                },
21565            ]))
21566        });
21567    let navigated = cx
21568        .update_editor(|editor, window, cx| {
21569            editor.find_all_references(&FindAllReferences, window, cx)
21570        })
21571        .unwrap()
21572        .await
21573        .expect("Failed to navigate to references");
21574    assert_eq!(
21575        navigated,
21576        Navigated::Yes,
21577        "Should have navigated to references from the FindAllReferences response"
21578    );
21579    cx.assert_editor_state(
21580        &r#"fn one() {
21581            let mut a = two();
21582        }
21583
21584        fn ˇtwo() {}"#
21585            .unindent(),
21586    );
21587
21588    let editors = cx.update_workspace(|workspace, _, cx| {
21589        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21590    });
21591    cx.update_editor(|_, _, _| {
21592        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21593    });
21594
21595    cx.set_state(
21596        &r#"fn one() {
21597            let mut a = ˇtwo();
21598        }
21599
21600        fn two() {}"#
21601            .unindent(),
21602    );
21603    let navigated = cx
21604        .update_editor(|editor, window, cx| {
21605            editor.find_all_references(&FindAllReferences, window, cx)
21606        })
21607        .unwrap()
21608        .await
21609        .expect("Failed to navigate to references");
21610    assert_eq!(
21611        navigated,
21612        Navigated::Yes,
21613        "Should have navigated to references from the FindAllReferences response"
21614    );
21615    cx.assert_editor_state(
21616        &r#"fn one() {
21617            let mut a = ˇtwo();
21618        }
21619
21620        fn two() {}"#
21621            .unindent(),
21622    );
21623    let editors = cx.update_workspace(|workspace, _, cx| {
21624        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21625    });
21626    cx.update_editor(|_, _, _| {
21627        assert_eq!(
21628            editors.len(),
21629            2,
21630            "should have re-used the previous multibuffer"
21631        );
21632    });
21633
21634    cx.set_state(
21635        &r#"fn one() {
21636            let mut a = ˇtwo();
21637        }
21638        fn three() {}
21639        fn two() {}"#
21640            .unindent(),
21641    );
21642    cx.lsp
21643        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21644            Ok(Some(vec![
21645                lsp::Location {
21646                    uri: params.text_document_position.text_document.uri.clone(),
21647                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21648                },
21649                lsp::Location {
21650                    uri: params.text_document_position.text_document.uri,
21651                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21652                },
21653            ]))
21654        });
21655    let navigated = cx
21656        .update_editor(|editor, window, cx| {
21657            editor.find_all_references(&FindAllReferences, window, cx)
21658        })
21659        .unwrap()
21660        .await
21661        .expect("Failed to navigate to references");
21662    assert_eq!(
21663        navigated,
21664        Navigated::Yes,
21665        "Should have navigated to references from the FindAllReferences response"
21666    );
21667    cx.assert_editor_state(
21668        &r#"fn one() {
21669                let mut a = ˇtwo();
21670            }
21671            fn three() {}
21672            fn two() {}"#
21673            .unindent(),
21674    );
21675    let editors = cx.update_workspace(|workspace, _, cx| {
21676        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21677    });
21678    cx.update_editor(|_, _, _| {
21679        assert_eq!(
21680            editors.len(),
21681            3,
21682            "should have used a new multibuffer as offsets changed"
21683        );
21684    });
21685}
21686#[gpui::test]
21687async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21688    init_test(cx, |_| {});
21689
21690    let language = Arc::new(Language::new(
21691        LanguageConfig::default(),
21692        Some(tree_sitter_rust::LANGUAGE.into()),
21693    ));
21694
21695    let text = r#"
21696        #[cfg(test)]
21697        mod tests() {
21698            #[test]
21699            fn runnable_1() {
21700                let a = 1;
21701            }
21702
21703            #[test]
21704            fn runnable_2() {
21705                let a = 1;
21706                let b = 2;
21707            }
21708        }
21709    "#
21710    .unindent();
21711
21712    let fs = FakeFs::new(cx.executor());
21713    fs.insert_file("/file.rs", Default::default()).await;
21714
21715    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21716    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21717    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21718    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21719    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21720
21721    let editor = cx.new_window_entity(|window, cx| {
21722        Editor::new(
21723            EditorMode::full(),
21724            multi_buffer,
21725            Some(project.clone()),
21726            window,
21727            cx,
21728        )
21729    });
21730
21731    editor.update_in(cx, |editor, window, cx| {
21732        let snapshot = editor.buffer().read(cx).snapshot(cx);
21733        editor.tasks.insert(
21734            (buffer.read(cx).remote_id(), 3),
21735            RunnableTasks {
21736                templates: vec![],
21737                offset: snapshot.anchor_before(43),
21738                column: 0,
21739                extra_variables: HashMap::default(),
21740                context_range: BufferOffset(43)..BufferOffset(85),
21741            },
21742        );
21743        editor.tasks.insert(
21744            (buffer.read(cx).remote_id(), 8),
21745            RunnableTasks {
21746                templates: vec![],
21747                offset: snapshot.anchor_before(86),
21748                column: 0,
21749                extra_variables: HashMap::default(),
21750                context_range: BufferOffset(86)..BufferOffset(191),
21751            },
21752        );
21753
21754        // Test finding task when cursor is inside function body
21755        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21756            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21757        });
21758        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21759        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21760
21761        // Test finding task when cursor is on function name
21762        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21763            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21764        });
21765        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21766        assert_eq!(row, 8, "Should find task when cursor is on function name");
21767    });
21768}
21769
21770#[gpui::test]
21771async fn test_folding_buffers(cx: &mut TestAppContext) {
21772    init_test(cx, |_| {});
21773
21774    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21775    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21776    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21777
21778    let fs = FakeFs::new(cx.executor());
21779    fs.insert_tree(
21780        path!("/a"),
21781        json!({
21782            "first.rs": sample_text_1,
21783            "second.rs": sample_text_2,
21784            "third.rs": sample_text_3,
21785        }),
21786    )
21787    .await;
21788    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21789    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21790    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21791    let worktree = project.update(cx, |project, cx| {
21792        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21793        assert_eq!(worktrees.len(), 1);
21794        worktrees.pop().unwrap()
21795    });
21796    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21797
21798    let buffer_1 = project
21799        .update(cx, |project, cx| {
21800            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
21801        })
21802        .await
21803        .unwrap();
21804    let buffer_2 = project
21805        .update(cx, |project, cx| {
21806            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
21807        })
21808        .await
21809        .unwrap();
21810    let buffer_3 = project
21811        .update(cx, |project, cx| {
21812            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
21813        })
21814        .await
21815        .unwrap();
21816
21817    let multi_buffer = cx.new(|cx| {
21818        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21819        multi_buffer.push_excerpts(
21820            buffer_1.clone(),
21821            [
21822                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21823                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21824                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21825            ],
21826            cx,
21827        );
21828        multi_buffer.push_excerpts(
21829            buffer_2.clone(),
21830            [
21831                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21832                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21833                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21834            ],
21835            cx,
21836        );
21837        multi_buffer.push_excerpts(
21838            buffer_3.clone(),
21839            [
21840                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21841                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21842                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21843            ],
21844            cx,
21845        );
21846        multi_buffer
21847    });
21848    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21849        Editor::new(
21850            EditorMode::full(),
21851            multi_buffer.clone(),
21852            Some(project.clone()),
21853            window,
21854            cx,
21855        )
21856    });
21857
21858    assert_eq!(
21859        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21860        "\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",
21861    );
21862
21863    multi_buffer_editor.update(cx, |editor, cx| {
21864        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21865    });
21866    assert_eq!(
21867        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21868        "\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",
21869        "After folding the first buffer, its text should not be displayed"
21870    );
21871
21872    multi_buffer_editor.update(cx, |editor, cx| {
21873        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21874    });
21875    assert_eq!(
21876        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21877        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21878        "After folding the second buffer, its text should not be displayed"
21879    );
21880
21881    multi_buffer_editor.update(cx, |editor, cx| {
21882        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21883    });
21884    assert_eq!(
21885        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21886        "\n\n\n\n\n",
21887        "After folding the third buffer, its text should not be displayed"
21888    );
21889
21890    // Emulate selection inside the fold logic, that should work
21891    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21892        editor
21893            .snapshot(window, cx)
21894            .next_line_boundary(Point::new(0, 4));
21895    });
21896
21897    multi_buffer_editor.update(cx, |editor, cx| {
21898        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21899    });
21900    assert_eq!(
21901        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21902        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21903        "After unfolding the second buffer, its text should be displayed"
21904    );
21905
21906    // Typing inside of buffer 1 causes that buffer to be unfolded.
21907    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21908        assert_eq!(
21909            multi_buffer
21910                .read(cx)
21911                .snapshot(cx)
21912                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21913                .collect::<String>(),
21914            "bbbb"
21915        );
21916        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21917            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
21918        });
21919        editor.handle_input("B", window, cx);
21920    });
21921
21922    assert_eq!(
21923        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21924        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21925        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
21926    );
21927
21928    multi_buffer_editor.update(cx, |editor, cx| {
21929        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21930    });
21931    assert_eq!(
21932        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21933        "\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",
21934        "After unfolding the all buffers, all original text should be displayed"
21935    );
21936}
21937
21938#[gpui::test]
21939async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
21940    init_test(cx, |_| {});
21941
21942    let sample_text_1 = "1111\n2222\n3333".to_string();
21943    let sample_text_2 = "4444\n5555\n6666".to_string();
21944    let sample_text_3 = "7777\n8888\n9999".to_string();
21945
21946    let fs = FakeFs::new(cx.executor());
21947    fs.insert_tree(
21948        path!("/a"),
21949        json!({
21950            "first.rs": sample_text_1,
21951            "second.rs": sample_text_2,
21952            "third.rs": sample_text_3,
21953        }),
21954    )
21955    .await;
21956    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21957    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21958    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21959    let worktree = project.update(cx, |project, cx| {
21960        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21961        assert_eq!(worktrees.len(), 1);
21962        worktrees.pop().unwrap()
21963    });
21964    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21965
21966    let buffer_1 = project
21967        .update(cx, |project, cx| {
21968            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
21969        })
21970        .await
21971        .unwrap();
21972    let buffer_2 = project
21973        .update(cx, |project, cx| {
21974            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
21975        })
21976        .await
21977        .unwrap();
21978    let buffer_3 = project
21979        .update(cx, |project, cx| {
21980            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
21981        })
21982        .await
21983        .unwrap();
21984
21985    let multi_buffer = cx.new(|cx| {
21986        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21987        multi_buffer.push_excerpts(
21988            buffer_1.clone(),
21989            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21990            cx,
21991        );
21992        multi_buffer.push_excerpts(
21993            buffer_2.clone(),
21994            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21995            cx,
21996        );
21997        multi_buffer.push_excerpts(
21998            buffer_3.clone(),
21999            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22000            cx,
22001        );
22002        multi_buffer
22003    });
22004
22005    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22006        Editor::new(
22007            EditorMode::full(),
22008            multi_buffer,
22009            Some(project.clone()),
22010            window,
22011            cx,
22012        )
22013    });
22014
22015    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22016    assert_eq!(
22017        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22018        full_text,
22019    );
22020
22021    multi_buffer_editor.update(cx, |editor, cx| {
22022        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22023    });
22024    assert_eq!(
22025        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22026        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22027        "After folding the first buffer, its text should not be displayed"
22028    );
22029
22030    multi_buffer_editor.update(cx, |editor, cx| {
22031        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22032    });
22033
22034    assert_eq!(
22035        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22036        "\n\n\n\n\n\n7777\n8888\n9999",
22037        "After folding the second buffer, its text should not be displayed"
22038    );
22039
22040    multi_buffer_editor.update(cx, |editor, cx| {
22041        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22042    });
22043    assert_eq!(
22044        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22045        "\n\n\n\n\n",
22046        "After folding the third buffer, its text should not be displayed"
22047    );
22048
22049    multi_buffer_editor.update(cx, |editor, cx| {
22050        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22051    });
22052    assert_eq!(
22053        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22054        "\n\n\n\n4444\n5555\n6666\n\n",
22055        "After unfolding the second buffer, its text should be displayed"
22056    );
22057
22058    multi_buffer_editor.update(cx, |editor, cx| {
22059        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22060    });
22061    assert_eq!(
22062        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22063        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22064        "After unfolding the first buffer, its text should be displayed"
22065    );
22066
22067    multi_buffer_editor.update(cx, |editor, cx| {
22068        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22069    });
22070    assert_eq!(
22071        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22072        full_text,
22073        "After unfolding all buffers, all original text should be displayed"
22074    );
22075}
22076
22077#[gpui::test]
22078async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22079    init_test(cx, |_| {});
22080
22081    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22082
22083    let fs = FakeFs::new(cx.executor());
22084    fs.insert_tree(
22085        path!("/a"),
22086        json!({
22087            "main.rs": sample_text,
22088        }),
22089    )
22090    .await;
22091    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22092    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22093    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22094    let worktree = project.update(cx, |project, cx| {
22095        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22096        assert_eq!(worktrees.len(), 1);
22097        worktrees.pop().unwrap()
22098    });
22099    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22100
22101    let buffer_1 = project
22102        .update(cx, |project, cx| {
22103            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22104        })
22105        .await
22106        .unwrap();
22107
22108    let multi_buffer = cx.new(|cx| {
22109        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22110        multi_buffer.push_excerpts(
22111            buffer_1.clone(),
22112            [ExcerptRange::new(
22113                Point::new(0, 0)
22114                    ..Point::new(
22115                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22116                        0,
22117                    ),
22118            )],
22119            cx,
22120        );
22121        multi_buffer
22122    });
22123    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22124        Editor::new(
22125            EditorMode::full(),
22126            multi_buffer,
22127            Some(project.clone()),
22128            window,
22129            cx,
22130        )
22131    });
22132
22133    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22134    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22135        enum TestHighlight {}
22136        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22137        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22138        editor.highlight_text::<TestHighlight>(
22139            vec![highlight_range.clone()],
22140            HighlightStyle::color(Hsla::green()),
22141            cx,
22142        );
22143        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22144            s.select_ranges(Some(highlight_range))
22145        });
22146    });
22147
22148    let full_text = format!("\n\n{sample_text}");
22149    assert_eq!(
22150        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22151        full_text,
22152    );
22153}
22154
22155#[gpui::test]
22156async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22157    init_test(cx, |_| {});
22158    cx.update(|cx| {
22159        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22160            "keymaps/default-linux.json",
22161            cx,
22162        )
22163        .unwrap();
22164        cx.bind_keys(default_key_bindings);
22165    });
22166
22167    let (editor, cx) = cx.add_window_view(|window, cx| {
22168        let multi_buffer = MultiBuffer::build_multi(
22169            [
22170                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22171                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22172                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22173                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22174            ],
22175            cx,
22176        );
22177        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22178
22179        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22180        // fold all but the second buffer, so that we test navigating between two
22181        // adjacent folded buffers, as well as folded buffers at the start and
22182        // end the multibuffer
22183        editor.fold_buffer(buffer_ids[0], cx);
22184        editor.fold_buffer(buffer_ids[2], cx);
22185        editor.fold_buffer(buffer_ids[3], cx);
22186
22187        editor
22188    });
22189    cx.simulate_resize(size(px(1000.), px(1000.)));
22190
22191    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22192    cx.assert_excerpts_with_selections(indoc! {"
22193        [EXCERPT]
22194        ˇ[FOLDED]
22195        [EXCERPT]
22196        a1
22197        b1
22198        [EXCERPT]
22199        [FOLDED]
22200        [EXCERPT]
22201        [FOLDED]
22202        "
22203    });
22204    cx.simulate_keystroke("down");
22205    cx.assert_excerpts_with_selections(indoc! {"
22206        [EXCERPT]
22207        [FOLDED]
22208        [EXCERPT]
22209        ˇa1
22210        b1
22211        [EXCERPT]
22212        [FOLDED]
22213        [EXCERPT]
22214        [FOLDED]
22215        "
22216    });
22217    cx.simulate_keystroke("down");
22218    cx.assert_excerpts_with_selections(indoc! {"
22219        [EXCERPT]
22220        [FOLDED]
22221        [EXCERPT]
22222        a1
22223        ˇb1
22224        [EXCERPT]
22225        [FOLDED]
22226        [EXCERPT]
22227        [FOLDED]
22228        "
22229    });
22230    cx.simulate_keystroke("down");
22231    cx.assert_excerpts_with_selections(indoc! {"
22232        [EXCERPT]
22233        [FOLDED]
22234        [EXCERPT]
22235        a1
22236        b1
22237        ˇ[EXCERPT]
22238        [FOLDED]
22239        [EXCERPT]
22240        [FOLDED]
22241        "
22242    });
22243    cx.simulate_keystroke("down");
22244    cx.assert_excerpts_with_selections(indoc! {"
22245        [EXCERPT]
22246        [FOLDED]
22247        [EXCERPT]
22248        a1
22249        b1
22250        [EXCERPT]
22251        ˇ[FOLDED]
22252        [EXCERPT]
22253        [FOLDED]
22254        "
22255    });
22256    for _ in 0..5 {
22257        cx.simulate_keystroke("down");
22258        cx.assert_excerpts_with_selections(indoc! {"
22259            [EXCERPT]
22260            [FOLDED]
22261            [EXCERPT]
22262            a1
22263            b1
22264            [EXCERPT]
22265            [FOLDED]
22266            [EXCERPT]
22267            ˇ[FOLDED]
22268            "
22269        });
22270    }
22271
22272    cx.simulate_keystroke("up");
22273    cx.assert_excerpts_with_selections(indoc! {"
22274        [EXCERPT]
22275        [FOLDED]
22276        [EXCERPT]
22277        a1
22278        b1
22279        [EXCERPT]
22280        ˇ[FOLDED]
22281        [EXCERPT]
22282        [FOLDED]
22283        "
22284    });
22285    cx.simulate_keystroke("up");
22286    cx.assert_excerpts_with_selections(indoc! {"
22287        [EXCERPT]
22288        [FOLDED]
22289        [EXCERPT]
22290        a1
22291        b1
22292        ˇ[EXCERPT]
22293        [FOLDED]
22294        [EXCERPT]
22295        [FOLDED]
22296        "
22297    });
22298    cx.simulate_keystroke("up");
22299    cx.assert_excerpts_with_selections(indoc! {"
22300        [EXCERPT]
22301        [FOLDED]
22302        [EXCERPT]
22303        a1
22304        ˇb1
22305        [EXCERPT]
22306        [FOLDED]
22307        [EXCERPT]
22308        [FOLDED]
22309        "
22310    });
22311    cx.simulate_keystroke("up");
22312    cx.assert_excerpts_with_selections(indoc! {"
22313        [EXCERPT]
22314        [FOLDED]
22315        [EXCERPT]
22316        ˇa1
22317        b1
22318        [EXCERPT]
22319        [FOLDED]
22320        [EXCERPT]
22321        [FOLDED]
22322        "
22323    });
22324    for _ in 0..5 {
22325        cx.simulate_keystroke("up");
22326        cx.assert_excerpts_with_selections(indoc! {"
22327            [EXCERPT]
22328            ˇ[FOLDED]
22329            [EXCERPT]
22330            a1
22331            b1
22332            [EXCERPT]
22333            [FOLDED]
22334            [EXCERPT]
22335            [FOLDED]
22336            "
22337        });
22338    }
22339}
22340
22341#[gpui::test]
22342async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22343    init_test(cx, |_| {});
22344
22345    // Simple insertion
22346    assert_highlighted_edits(
22347        "Hello, world!",
22348        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22349        true,
22350        cx,
22351        |highlighted_edits, cx| {
22352            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22353            assert_eq!(highlighted_edits.highlights.len(), 1);
22354            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22355            assert_eq!(
22356                highlighted_edits.highlights[0].1.background_color,
22357                Some(cx.theme().status().created_background)
22358            );
22359        },
22360    )
22361    .await;
22362
22363    // Replacement
22364    assert_highlighted_edits(
22365        "This is a test.",
22366        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22367        false,
22368        cx,
22369        |highlighted_edits, cx| {
22370            assert_eq!(highlighted_edits.text, "That is a test.");
22371            assert_eq!(highlighted_edits.highlights.len(), 1);
22372            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22373            assert_eq!(
22374                highlighted_edits.highlights[0].1.background_color,
22375                Some(cx.theme().status().created_background)
22376            );
22377        },
22378    )
22379    .await;
22380
22381    // Multiple edits
22382    assert_highlighted_edits(
22383        "Hello, world!",
22384        vec![
22385            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22386            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22387        ],
22388        false,
22389        cx,
22390        |highlighted_edits, cx| {
22391            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22392            assert_eq!(highlighted_edits.highlights.len(), 2);
22393            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22394            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22395            assert_eq!(
22396                highlighted_edits.highlights[0].1.background_color,
22397                Some(cx.theme().status().created_background)
22398            );
22399            assert_eq!(
22400                highlighted_edits.highlights[1].1.background_color,
22401                Some(cx.theme().status().created_background)
22402            );
22403        },
22404    )
22405    .await;
22406
22407    // Multiple lines with edits
22408    assert_highlighted_edits(
22409        "First line\nSecond line\nThird line\nFourth line",
22410        vec![
22411            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22412            (
22413                Point::new(2, 0)..Point::new(2, 10),
22414                "New third line".to_string(),
22415            ),
22416            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22417        ],
22418        false,
22419        cx,
22420        |highlighted_edits, cx| {
22421            assert_eq!(
22422                highlighted_edits.text,
22423                "Second modified\nNew third line\nFourth updated line"
22424            );
22425            assert_eq!(highlighted_edits.highlights.len(), 3);
22426            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22427            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22428            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22429            for highlight in &highlighted_edits.highlights {
22430                assert_eq!(
22431                    highlight.1.background_color,
22432                    Some(cx.theme().status().created_background)
22433                );
22434            }
22435        },
22436    )
22437    .await;
22438}
22439
22440#[gpui::test]
22441async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22442    init_test(cx, |_| {});
22443
22444    // Deletion
22445    assert_highlighted_edits(
22446        "Hello, world!",
22447        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22448        true,
22449        cx,
22450        |highlighted_edits, cx| {
22451            assert_eq!(highlighted_edits.text, "Hello, world!");
22452            assert_eq!(highlighted_edits.highlights.len(), 1);
22453            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22454            assert_eq!(
22455                highlighted_edits.highlights[0].1.background_color,
22456                Some(cx.theme().status().deleted_background)
22457            );
22458        },
22459    )
22460    .await;
22461
22462    // Insertion
22463    assert_highlighted_edits(
22464        "Hello, world!",
22465        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22466        true,
22467        cx,
22468        |highlighted_edits, cx| {
22469            assert_eq!(highlighted_edits.highlights.len(), 1);
22470            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22471            assert_eq!(
22472                highlighted_edits.highlights[0].1.background_color,
22473                Some(cx.theme().status().created_background)
22474            );
22475        },
22476    )
22477    .await;
22478}
22479
22480async fn assert_highlighted_edits(
22481    text: &str,
22482    edits: Vec<(Range<Point>, String)>,
22483    include_deletions: bool,
22484    cx: &mut TestAppContext,
22485    assertion_fn: impl Fn(HighlightedText, &App),
22486) {
22487    let window = cx.add_window(|window, cx| {
22488        let buffer = MultiBuffer::build_simple(text, cx);
22489        Editor::new(EditorMode::full(), buffer, None, window, cx)
22490    });
22491    let cx = &mut VisualTestContext::from_window(*window, cx);
22492
22493    let (buffer, snapshot) = window
22494        .update(cx, |editor, _window, cx| {
22495            (
22496                editor.buffer().clone(),
22497                editor.buffer().read(cx).snapshot(cx),
22498            )
22499        })
22500        .unwrap();
22501
22502    let edits = edits
22503        .into_iter()
22504        .map(|(range, edit)| {
22505            (
22506                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22507                edit,
22508            )
22509        })
22510        .collect::<Vec<_>>();
22511
22512    let text_anchor_edits = edits
22513        .clone()
22514        .into_iter()
22515        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22516        .collect::<Vec<_>>();
22517
22518    let edit_preview = window
22519        .update(cx, |_, _window, cx| {
22520            buffer
22521                .read(cx)
22522                .as_singleton()
22523                .unwrap()
22524                .read(cx)
22525                .preview_edits(text_anchor_edits.into(), cx)
22526        })
22527        .unwrap()
22528        .await;
22529
22530    cx.update(|_window, cx| {
22531        let highlighted_edits = edit_prediction_edit_text(
22532            snapshot.as_singleton().unwrap().2,
22533            &edits,
22534            &edit_preview,
22535            include_deletions,
22536            cx,
22537        );
22538        assertion_fn(highlighted_edits, cx)
22539    });
22540}
22541
22542#[track_caller]
22543fn assert_breakpoint(
22544    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22545    path: &Arc<Path>,
22546    expected: Vec<(u32, Breakpoint)>,
22547) {
22548    if expected.is_empty() {
22549        assert!(!breakpoints.contains_key(path), "{}", path.display());
22550    } else {
22551        let mut breakpoint = breakpoints
22552            .get(path)
22553            .unwrap()
22554            .iter()
22555            .map(|breakpoint| {
22556                (
22557                    breakpoint.row,
22558                    Breakpoint {
22559                        message: breakpoint.message.clone(),
22560                        state: breakpoint.state,
22561                        condition: breakpoint.condition.clone(),
22562                        hit_condition: breakpoint.hit_condition.clone(),
22563                    },
22564                )
22565            })
22566            .collect::<Vec<_>>();
22567
22568        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22569
22570        assert_eq!(expected, breakpoint);
22571    }
22572}
22573
22574fn add_log_breakpoint_at_cursor(
22575    editor: &mut Editor,
22576    log_message: &str,
22577    window: &mut Window,
22578    cx: &mut Context<Editor>,
22579) {
22580    let (anchor, bp) = editor
22581        .breakpoints_at_cursors(window, cx)
22582        .first()
22583        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22584        .unwrap_or_else(|| {
22585            let cursor_position: Point = editor.selections.newest(cx).head();
22586
22587            let breakpoint_position = editor
22588                .snapshot(window, cx)
22589                .display_snapshot
22590                .buffer_snapshot()
22591                .anchor_before(Point::new(cursor_position.row, 0));
22592
22593            (breakpoint_position, Breakpoint::new_log(log_message))
22594        });
22595
22596    editor.edit_breakpoint_at_anchor(
22597        anchor,
22598        bp,
22599        BreakpointEditAction::EditLogMessage(log_message.into()),
22600        cx,
22601    );
22602}
22603
22604#[gpui::test]
22605async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22606    init_test(cx, |_| {});
22607
22608    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22609    let fs = FakeFs::new(cx.executor());
22610    fs.insert_tree(
22611        path!("/a"),
22612        json!({
22613            "main.rs": sample_text,
22614        }),
22615    )
22616    .await;
22617    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22618    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22619    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22620
22621    let fs = FakeFs::new(cx.executor());
22622    fs.insert_tree(
22623        path!("/a"),
22624        json!({
22625            "main.rs": sample_text,
22626        }),
22627    )
22628    .await;
22629    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22630    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22631    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22632    let worktree_id = workspace
22633        .update(cx, |workspace, _window, cx| {
22634            workspace.project().update(cx, |project, cx| {
22635                project.worktrees(cx).next().unwrap().read(cx).id()
22636            })
22637        })
22638        .unwrap();
22639
22640    let buffer = project
22641        .update(cx, |project, cx| {
22642            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22643        })
22644        .await
22645        .unwrap();
22646
22647    let (editor, cx) = cx.add_window_view(|window, cx| {
22648        Editor::new(
22649            EditorMode::full(),
22650            MultiBuffer::build_from_buffer(buffer, cx),
22651            Some(project.clone()),
22652            window,
22653            cx,
22654        )
22655    });
22656
22657    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22658    let abs_path = project.read_with(cx, |project, cx| {
22659        project
22660            .absolute_path(&project_path, cx)
22661            .map(Arc::from)
22662            .unwrap()
22663    });
22664
22665    // assert we can add breakpoint on the first line
22666    editor.update_in(cx, |editor, window, cx| {
22667        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22668        editor.move_to_end(&MoveToEnd, window, cx);
22669        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22670    });
22671
22672    let breakpoints = editor.update(cx, |editor, cx| {
22673        editor
22674            .breakpoint_store()
22675            .as_ref()
22676            .unwrap()
22677            .read(cx)
22678            .all_source_breakpoints(cx)
22679    });
22680
22681    assert_eq!(1, breakpoints.len());
22682    assert_breakpoint(
22683        &breakpoints,
22684        &abs_path,
22685        vec![
22686            (0, Breakpoint::new_standard()),
22687            (3, Breakpoint::new_standard()),
22688        ],
22689    );
22690
22691    editor.update_in(cx, |editor, window, cx| {
22692        editor.move_to_beginning(&MoveToBeginning, window, cx);
22693        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22694    });
22695
22696    let breakpoints = editor.update(cx, |editor, cx| {
22697        editor
22698            .breakpoint_store()
22699            .as_ref()
22700            .unwrap()
22701            .read(cx)
22702            .all_source_breakpoints(cx)
22703    });
22704
22705    assert_eq!(1, breakpoints.len());
22706    assert_breakpoint(
22707        &breakpoints,
22708        &abs_path,
22709        vec![(3, Breakpoint::new_standard())],
22710    );
22711
22712    editor.update_in(cx, |editor, window, cx| {
22713        editor.move_to_end(&MoveToEnd, window, cx);
22714        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22715    });
22716
22717    let breakpoints = editor.update(cx, |editor, cx| {
22718        editor
22719            .breakpoint_store()
22720            .as_ref()
22721            .unwrap()
22722            .read(cx)
22723            .all_source_breakpoints(cx)
22724    });
22725
22726    assert_eq!(0, breakpoints.len());
22727    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22728}
22729
22730#[gpui::test]
22731async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22732    init_test(cx, |_| {});
22733
22734    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22735
22736    let fs = FakeFs::new(cx.executor());
22737    fs.insert_tree(
22738        path!("/a"),
22739        json!({
22740            "main.rs": sample_text,
22741        }),
22742    )
22743    .await;
22744    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22745    let (workspace, cx) =
22746        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22747
22748    let worktree_id = workspace.update(cx, |workspace, cx| {
22749        workspace.project().update(cx, |project, cx| {
22750            project.worktrees(cx).next().unwrap().read(cx).id()
22751        })
22752    });
22753
22754    let buffer = project
22755        .update(cx, |project, cx| {
22756            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22757        })
22758        .await
22759        .unwrap();
22760
22761    let (editor, cx) = cx.add_window_view(|window, cx| {
22762        Editor::new(
22763            EditorMode::full(),
22764            MultiBuffer::build_from_buffer(buffer, cx),
22765            Some(project.clone()),
22766            window,
22767            cx,
22768        )
22769    });
22770
22771    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22772    let abs_path = project.read_with(cx, |project, cx| {
22773        project
22774            .absolute_path(&project_path, cx)
22775            .map(Arc::from)
22776            .unwrap()
22777    });
22778
22779    editor.update_in(cx, |editor, window, cx| {
22780        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22781    });
22782
22783    let breakpoints = editor.update(cx, |editor, cx| {
22784        editor
22785            .breakpoint_store()
22786            .as_ref()
22787            .unwrap()
22788            .read(cx)
22789            .all_source_breakpoints(cx)
22790    });
22791
22792    assert_breakpoint(
22793        &breakpoints,
22794        &abs_path,
22795        vec![(0, Breakpoint::new_log("hello world"))],
22796    );
22797
22798    // Removing a log message from a log breakpoint should remove it
22799    editor.update_in(cx, |editor, window, cx| {
22800        add_log_breakpoint_at_cursor(editor, "", 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_breakpoint(&breakpoints, &abs_path, vec![]);
22813
22814    editor.update_in(cx, |editor, window, cx| {
22815        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22816        editor.move_to_end(&MoveToEnd, window, cx);
22817        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22818        // Not adding a log message to a standard breakpoint shouldn't remove it
22819        add_log_breakpoint_at_cursor(editor, "", window, cx);
22820    });
22821
22822    let breakpoints = editor.update(cx, |editor, cx| {
22823        editor
22824            .breakpoint_store()
22825            .as_ref()
22826            .unwrap()
22827            .read(cx)
22828            .all_source_breakpoints(cx)
22829    });
22830
22831    assert_breakpoint(
22832        &breakpoints,
22833        &abs_path,
22834        vec![
22835            (0, Breakpoint::new_standard()),
22836            (3, Breakpoint::new_standard()),
22837        ],
22838    );
22839
22840    editor.update_in(cx, |editor, window, cx| {
22841        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22842    });
22843
22844    let breakpoints = editor.update(cx, |editor, cx| {
22845        editor
22846            .breakpoint_store()
22847            .as_ref()
22848            .unwrap()
22849            .read(cx)
22850            .all_source_breakpoints(cx)
22851    });
22852
22853    assert_breakpoint(
22854        &breakpoints,
22855        &abs_path,
22856        vec![
22857            (0, Breakpoint::new_standard()),
22858            (3, Breakpoint::new_log("hello world")),
22859        ],
22860    );
22861
22862    editor.update_in(cx, |editor, window, cx| {
22863        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22864    });
22865
22866    let breakpoints = editor.update(cx, |editor, cx| {
22867        editor
22868            .breakpoint_store()
22869            .as_ref()
22870            .unwrap()
22871            .read(cx)
22872            .all_source_breakpoints(cx)
22873    });
22874
22875    assert_breakpoint(
22876        &breakpoints,
22877        &abs_path,
22878        vec![
22879            (0, Breakpoint::new_standard()),
22880            (3, Breakpoint::new_log("hello Earth!!")),
22881        ],
22882    );
22883}
22884
22885/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22886/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22887/// or when breakpoints were placed out of order. This tests for a regression too
22888#[gpui::test]
22889async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22890    init_test(cx, |_| {});
22891
22892    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22893    let fs = FakeFs::new(cx.executor());
22894    fs.insert_tree(
22895        path!("/a"),
22896        json!({
22897            "main.rs": sample_text,
22898        }),
22899    )
22900    .await;
22901    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22902    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22903    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22904
22905    let fs = FakeFs::new(cx.executor());
22906    fs.insert_tree(
22907        path!("/a"),
22908        json!({
22909            "main.rs": sample_text,
22910        }),
22911    )
22912    .await;
22913    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22914    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22915    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22916    let worktree_id = workspace
22917        .update(cx, |workspace, _window, cx| {
22918            workspace.project().update(cx, |project, cx| {
22919                project.worktrees(cx).next().unwrap().read(cx).id()
22920            })
22921        })
22922        .unwrap();
22923
22924    let buffer = project
22925        .update(cx, |project, cx| {
22926            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22927        })
22928        .await
22929        .unwrap();
22930
22931    let (editor, cx) = cx.add_window_view(|window, cx| {
22932        Editor::new(
22933            EditorMode::full(),
22934            MultiBuffer::build_from_buffer(buffer, cx),
22935            Some(project.clone()),
22936            window,
22937            cx,
22938        )
22939    });
22940
22941    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22942    let abs_path = project.read_with(cx, |project, cx| {
22943        project
22944            .absolute_path(&project_path, cx)
22945            .map(Arc::from)
22946            .unwrap()
22947    });
22948
22949    // assert we can add breakpoint on the first line
22950    editor.update_in(cx, |editor, window, cx| {
22951        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22952        editor.move_to_end(&MoveToEnd, window, cx);
22953        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22954        editor.move_up(&MoveUp, window, cx);
22955        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22956    });
22957
22958    let breakpoints = editor.update(cx, |editor, cx| {
22959        editor
22960            .breakpoint_store()
22961            .as_ref()
22962            .unwrap()
22963            .read(cx)
22964            .all_source_breakpoints(cx)
22965    });
22966
22967    assert_eq!(1, breakpoints.len());
22968    assert_breakpoint(
22969        &breakpoints,
22970        &abs_path,
22971        vec![
22972            (0, Breakpoint::new_standard()),
22973            (2, Breakpoint::new_standard()),
22974            (3, Breakpoint::new_standard()),
22975        ],
22976    );
22977
22978    editor.update_in(cx, |editor, window, cx| {
22979        editor.move_to_beginning(&MoveToBeginning, window, cx);
22980        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22981        editor.move_to_end(&MoveToEnd, window, cx);
22982        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22983        // Disabling a breakpoint that doesn't exist should do nothing
22984        editor.move_up(&MoveUp, window, cx);
22985        editor.move_up(&MoveUp, window, cx);
22986        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22987    });
22988
22989    let breakpoints = editor.update(cx, |editor, cx| {
22990        editor
22991            .breakpoint_store()
22992            .as_ref()
22993            .unwrap()
22994            .read(cx)
22995            .all_source_breakpoints(cx)
22996    });
22997
22998    let disable_breakpoint = {
22999        let mut bp = Breakpoint::new_standard();
23000        bp.state = BreakpointState::Disabled;
23001        bp
23002    };
23003
23004    assert_eq!(1, breakpoints.len());
23005    assert_breakpoint(
23006        &breakpoints,
23007        &abs_path,
23008        vec![
23009            (0, disable_breakpoint.clone()),
23010            (2, Breakpoint::new_standard()),
23011            (3, disable_breakpoint.clone()),
23012        ],
23013    );
23014
23015    editor.update_in(cx, |editor, window, cx| {
23016        editor.move_to_beginning(&MoveToBeginning, window, cx);
23017        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23018        editor.move_to_end(&MoveToEnd, window, cx);
23019        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23020        editor.move_up(&MoveUp, window, cx);
23021        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23022    });
23023
23024    let breakpoints = editor.update(cx, |editor, cx| {
23025        editor
23026            .breakpoint_store()
23027            .as_ref()
23028            .unwrap()
23029            .read(cx)
23030            .all_source_breakpoints(cx)
23031    });
23032
23033    assert_eq!(1, breakpoints.len());
23034    assert_breakpoint(
23035        &breakpoints,
23036        &abs_path,
23037        vec![
23038            (0, Breakpoint::new_standard()),
23039            (2, disable_breakpoint),
23040            (3, Breakpoint::new_standard()),
23041        ],
23042    );
23043}
23044
23045#[gpui::test]
23046async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23047    init_test(cx, |_| {});
23048    let capabilities = lsp::ServerCapabilities {
23049        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23050            prepare_provider: Some(true),
23051            work_done_progress_options: Default::default(),
23052        })),
23053        ..Default::default()
23054    };
23055    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23056
23057    cx.set_state(indoc! {"
23058        struct Fˇoo {}
23059    "});
23060
23061    cx.update_editor(|editor, _, cx| {
23062        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23063        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23064        editor.highlight_background::<DocumentHighlightRead>(
23065            &[highlight_range],
23066            |theme| theme.colors().editor_document_highlight_read_background,
23067            cx,
23068        );
23069    });
23070
23071    let mut prepare_rename_handler = cx
23072        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23073            move |_, _, _| async move {
23074                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23075                    start: lsp::Position {
23076                        line: 0,
23077                        character: 7,
23078                    },
23079                    end: lsp::Position {
23080                        line: 0,
23081                        character: 10,
23082                    },
23083                })))
23084            },
23085        );
23086    let prepare_rename_task = cx
23087        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23088        .expect("Prepare rename was not started");
23089    prepare_rename_handler.next().await.unwrap();
23090    prepare_rename_task.await.expect("Prepare rename failed");
23091
23092    let mut rename_handler =
23093        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23094            let edit = lsp::TextEdit {
23095                range: lsp::Range {
23096                    start: lsp::Position {
23097                        line: 0,
23098                        character: 7,
23099                    },
23100                    end: lsp::Position {
23101                        line: 0,
23102                        character: 10,
23103                    },
23104                },
23105                new_text: "FooRenamed".to_string(),
23106            };
23107            Ok(Some(lsp::WorkspaceEdit::new(
23108                // Specify the same edit twice
23109                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23110            )))
23111        });
23112    let rename_task = cx
23113        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23114        .expect("Confirm rename was not started");
23115    rename_handler.next().await.unwrap();
23116    rename_task.await.expect("Confirm rename failed");
23117    cx.run_until_parked();
23118
23119    // Despite two edits, only one is actually applied as those are identical
23120    cx.assert_editor_state(indoc! {"
23121        struct FooRenamedˇ {}
23122    "});
23123}
23124
23125#[gpui::test]
23126async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23127    init_test(cx, |_| {});
23128    // These capabilities indicate that the server does not support prepare rename.
23129    let capabilities = lsp::ServerCapabilities {
23130        rename_provider: Some(lsp::OneOf::Left(true)),
23131        ..Default::default()
23132    };
23133    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23134
23135    cx.set_state(indoc! {"
23136        struct Fˇoo {}
23137    "});
23138
23139    cx.update_editor(|editor, _window, cx| {
23140        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23141        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23142        editor.highlight_background::<DocumentHighlightRead>(
23143            &[highlight_range],
23144            |theme| theme.colors().editor_document_highlight_read_background,
23145            cx,
23146        );
23147    });
23148
23149    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23150        .expect("Prepare rename was not started")
23151        .await
23152        .expect("Prepare rename failed");
23153
23154    let mut rename_handler =
23155        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23156            let edit = lsp::TextEdit {
23157                range: lsp::Range {
23158                    start: lsp::Position {
23159                        line: 0,
23160                        character: 7,
23161                    },
23162                    end: lsp::Position {
23163                        line: 0,
23164                        character: 10,
23165                    },
23166                },
23167                new_text: "FooRenamed".to_string(),
23168            };
23169            Ok(Some(lsp::WorkspaceEdit::new(
23170                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23171            )))
23172        });
23173    let rename_task = cx
23174        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23175        .expect("Confirm rename was not started");
23176    rename_handler.next().await.unwrap();
23177    rename_task.await.expect("Confirm rename failed");
23178    cx.run_until_parked();
23179
23180    // Correct range is renamed, as `surrounding_word` is used to find it.
23181    cx.assert_editor_state(indoc! {"
23182        struct FooRenamedˇ {}
23183    "});
23184}
23185
23186#[gpui::test]
23187async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23188    init_test(cx, |_| {});
23189    let mut cx = EditorTestContext::new(cx).await;
23190
23191    let language = Arc::new(
23192        Language::new(
23193            LanguageConfig::default(),
23194            Some(tree_sitter_html::LANGUAGE.into()),
23195        )
23196        .with_brackets_query(
23197            r#"
23198            ("<" @open "/>" @close)
23199            ("</" @open ">" @close)
23200            ("<" @open ">" @close)
23201            ("\"" @open "\"" @close)
23202            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23203        "#,
23204        )
23205        .unwrap(),
23206    );
23207    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23208
23209    cx.set_state(indoc! {"
23210        <span>ˇ</span>
23211    "});
23212    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23213    cx.assert_editor_state(indoc! {"
23214        <span>
23215        ˇ
23216        </span>
23217    "});
23218
23219    cx.set_state(indoc! {"
23220        <span><span></span>ˇ</span>
23221    "});
23222    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23223    cx.assert_editor_state(indoc! {"
23224        <span><span></span>
23225        ˇ</span>
23226    "});
23227
23228    cx.set_state(indoc! {"
23229        <span>ˇ
23230        </span>
23231    "});
23232    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23233    cx.assert_editor_state(indoc! {"
23234        <span>
23235        ˇ
23236        </span>
23237    "});
23238}
23239
23240#[gpui::test(iterations = 10)]
23241async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23242    init_test(cx, |_| {});
23243
23244    let fs = FakeFs::new(cx.executor());
23245    fs.insert_tree(
23246        path!("/dir"),
23247        json!({
23248            "a.ts": "a",
23249        }),
23250    )
23251    .await;
23252
23253    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23254    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23255    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23256
23257    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23258    language_registry.add(Arc::new(Language::new(
23259        LanguageConfig {
23260            name: "TypeScript".into(),
23261            matcher: LanguageMatcher {
23262                path_suffixes: vec!["ts".to_string()],
23263                ..Default::default()
23264            },
23265            ..Default::default()
23266        },
23267        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23268    )));
23269    let mut fake_language_servers = language_registry.register_fake_lsp(
23270        "TypeScript",
23271        FakeLspAdapter {
23272            capabilities: lsp::ServerCapabilities {
23273                code_lens_provider: Some(lsp::CodeLensOptions {
23274                    resolve_provider: Some(true),
23275                }),
23276                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23277                    commands: vec!["_the/command".to_string()],
23278                    ..lsp::ExecuteCommandOptions::default()
23279                }),
23280                ..lsp::ServerCapabilities::default()
23281            },
23282            ..FakeLspAdapter::default()
23283        },
23284    );
23285
23286    let editor = workspace
23287        .update(cx, |workspace, window, cx| {
23288            workspace.open_abs_path(
23289                PathBuf::from(path!("/dir/a.ts")),
23290                OpenOptions::default(),
23291                window,
23292                cx,
23293            )
23294        })
23295        .unwrap()
23296        .await
23297        .unwrap()
23298        .downcast::<Editor>()
23299        .unwrap();
23300    cx.executor().run_until_parked();
23301
23302    let fake_server = fake_language_servers.next().await.unwrap();
23303
23304    let buffer = editor.update(cx, |editor, cx| {
23305        editor
23306            .buffer()
23307            .read(cx)
23308            .as_singleton()
23309            .expect("have opened a single file by path")
23310    });
23311
23312    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23313    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23314    drop(buffer_snapshot);
23315    let actions = cx
23316        .update_window(*workspace, |_, window, cx| {
23317            project.code_actions(&buffer, anchor..anchor, window, cx)
23318        })
23319        .unwrap();
23320
23321    fake_server
23322        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23323            Ok(Some(vec![
23324                lsp::CodeLens {
23325                    range: lsp::Range::default(),
23326                    command: Some(lsp::Command {
23327                        title: "Code lens command".to_owned(),
23328                        command: "_the/command".to_owned(),
23329                        arguments: None,
23330                    }),
23331                    data: None,
23332                },
23333                lsp::CodeLens {
23334                    range: lsp::Range::default(),
23335                    command: Some(lsp::Command {
23336                        title: "Command not in capabilities".to_owned(),
23337                        command: "not in capabilities".to_owned(),
23338                        arguments: None,
23339                    }),
23340                    data: None,
23341                },
23342                lsp::CodeLens {
23343                    range: lsp::Range {
23344                        start: lsp::Position {
23345                            line: 1,
23346                            character: 1,
23347                        },
23348                        end: lsp::Position {
23349                            line: 1,
23350                            character: 1,
23351                        },
23352                    },
23353                    command: Some(lsp::Command {
23354                        title: "Command not in range".to_owned(),
23355                        command: "_the/command".to_owned(),
23356                        arguments: None,
23357                    }),
23358                    data: None,
23359                },
23360            ]))
23361        })
23362        .next()
23363        .await;
23364
23365    let actions = actions.await.unwrap();
23366    assert_eq!(
23367        actions.len(),
23368        1,
23369        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23370    );
23371    let action = actions[0].clone();
23372    let apply = project.update(cx, |project, cx| {
23373        project.apply_code_action(buffer.clone(), action, true, cx)
23374    });
23375
23376    // Resolving the code action does not populate its edits. In absence of
23377    // edits, we must execute the given command.
23378    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23379        |mut lens, _| async move {
23380            let lens_command = lens.command.as_mut().expect("should have a command");
23381            assert_eq!(lens_command.title, "Code lens command");
23382            lens_command.arguments = Some(vec![json!("the-argument")]);
23383            Ok(lens)
23384        },
23385    );
23386
23387    // While executing the command, the language server sends the editor
23388    // a `workspaceEdit` request.
23389    fake_server
23390        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23391            let fake = fake_server.clone();
23392            move |params, _| {
23393                assert_eq!(params.command, "_the/command");
23394                let fake = fake.clone();
23395                async move {
23396                    fake.server
23397                        .request::<lsp::request::ApplyWorkspaceEdit>(
23398                            lsp::ApplyWorkspaceEditParams {
23399                                label: None,
23400                                edit: lsp::WorkspaceEdit {
23401                                    changes: Some(
23402                                        [(
23403                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23404                                            vec![lsp::TextEdit {
23405                                                range: lsp::Range::new(
23406                                                    lsp::Position::new(0, 0),
23407                                                    lsp::Position::new(0, 0),
23408                                                ),
23409                                                new_text: "X".into(),
23410                                            }],
23411                                        )]
23412                                        .into_iter()
23413                                        .collect(),
23414                                    ),
23415                                    ..lsp::WorkspaceEdit::default()
23416                                },
23417                            },
23418                        )
23419                        .await
23420                        .into_response()
23421                        .unwrap();
23422                    Ok(Some(json!(null)))
23423                }
23424            }
23425        })
23426        .next()
23427        .await;
23428
23429    // Applying the code lens command returns a project transaction containing the edits
23430    // sent by the language server in its `workspaceEdit` request.
23431    let transaction = apply.await.unwrap();
23432    assert!(transaction.0.contains_key(&buffer));
23433    buffer.update(cx, |buffer, cx| {
23434        assert_eq!(buffer.text(), "Xa");
23435        buffer.undo(cx);
23436        assert_eq!(buffer.text(), "a");
23437    });
23438
23439    let actions_after_edits = cx
23440        .update_window(*workspace, |_, window, cx| {
23441            project.code_actions(&buffer, anchor..anchor, window, cx)
23442        })
23443        .unwrap()
23444        .await
23445        .unwrap();
23446    assert_eq!(
23447        actions, actions_after_edits,
23448        "For the same selection, same code lens actions should be returned"
23449    );
23450
23451    let _responses =
23452        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23453            panic!("No more code lens requests are expected");
23454        });
23455    editor.update_in(cx, |editor, window, cx| {
23456        editor.select_all(&SelectAll, window, cx);
23457    });
23458    cx.executor().run_until_parked();
23459    let new_actions = cx
23460        .update_window(*workspace, |_, window, cx| {
23461            project.code_actions(&buffer, anchor..anchor, window, cx)
23462        })
23463        .unwrap()
23464        .await
23465        .unwrap();
23466    assert_eq!(
23467        actions, new_actions,
23468        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23469    );
23470}
23471
23472#[gpui::test]
23473async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23474    init_test(cx, |_| {});
23475
23476    let fs = FakeFs::new(cx.executor());
23477    let main_text = r#"fn main() {
23478println!("1");
23479println!("2");
23480println!("3");
23481println!("4");
23482println!("5");
23483}"#;
23484    let lib_text = "mod foo {}";
23485    fs.insert_tree(
23486        path!("/a"),
23487        json!({
23488            "lib.rs": lib_text,
23489            "main.rs": main_text,
23490        }),
23491    )
23492    .await;
23493
23494    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23495    let (workspace, cx) =
23496        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23497    let worktree_id = workspace.update(cx, |workspace, cx| {
23498        workspace.project().update(cx, |project, cx| {
23499            project.worktrees(cx).next().unwrap().read(cx).id()
23500        })
23501    });
23502
23503    let expected_ranges = vec![
23504        Point::new(0, 0)..Point::new(0, 0),
23505        Point::new(1, 0)..Point::new(1, 1),
23506        Point::new(2, 0)..Point::new(2, 2),
23507        Point::new(3, 0)..Point::new(3, 3),
23508    ];
23509
23510    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23511    let editor_1 = workspace
23512        .update_in(cx, |workspace, window, cx| {
23513            workspace.open_path(
23514                (worktree_id, rel_path("main.rs")),
23515                Some(pane_1.downgrade()),
23516                true,
23517                window,
23518                cx,
23519            )
23520        })
23521        .unwrap()
23522        .await
23523        .downcast::<Editor>()
23524        .unwrap();
23525    pane_1.update(cx, |pane, cx| {
23526        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23527        open_editor.update(cx, |editor, cx| {
23528            assert_eq!(
23529                editor.display_text(cx),
23530                main_text,
23531                "Original main.rs text on initial open",
23532            );
23533            assert_eq!(
23534                editor
23535                    .selections
23536                    .all::<Point>(cx)
23537                    .into_iter()
23538                    .map(|s| s.range())
23539                    .collect::<Vec<_>>(),
23540                vec![Point::zero()..Point::zero()],
23541                "Default selections on initial open",
23542            );
23543        })
23544    });
23545    editor_1.update_in(cx, |editor, window, cx| {
23546        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23547            s.select_ranges(expected_ranges.clone());
23548        });
23549    });
23550
23551    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23552        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23553    });
23554    let editor_2 = workspace
23555        .update_in(cx, |workspace, window, cx| {
23556            workspace.open_path(
23557                (worktree_id, rel_path("main.rs")),
23558                Some(pane_2.downgrade()),
23559                true,
23560                window,
23561                cx,
23562            )
23563        })
23564        .unwrap()
23565        .await
23566        .downcast::<Editor>()
23567        .unwrap();
23568    pane_2.update(cx, |pane, cx| {
23569        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23570        open_editor.update(cx, |editor, cx| {
23571            assert_eq!(
23572                editor.display_text(cx),
23573                main_text,
23574                "Original main.rs text on initial open in another panel",
23575            );
23576            assert_eq!(
23577                editor
23578                    .selections
23579                    .all::<Point>(cx)
23580                    .into_iter()
23581                    .map(|s| s.range())
23582                    .collect::<Vec<_>>(),
23583                vec![Point::zero()..Point::zero()],
23584                "Default selections on initial open in another panel",
23585            );
23586        })
23587    });
23588
23589    editor_2.update_in(cx, |editor, window, cx| {
23590        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23591    });
23592
23593    let _other_editor_1 = workspace
23594        .update_in(cx, |workspace, window, cx| {
23595            workspace.open_path(
23596                (worktree_id, rel_path("lib.rs")),
23597                Some(pane_1.downgrade()),
23598                true,
23599                window,
23600                cx,
23601            )
23602        })
23603        .unwrap()
23604        .await
23605        .downcast::<Editor>()
23606        .unwrap();
23607    pane_1
23608        .update_in(cx, |pane, window, cx| {
23609            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23610        })
23611        .await
23612        .unwrap();
23613    drop(editor_1);
23614    pane_1.update(cx, |pane, cx| {
23615        pane.active_item()
23616            .unwrap()
23617            .downcast::<Editor>()
23618            .unwrap()
23619            .update(cx, |editor, cx| {
23620                assert_eq!(
23621                    editor.display_text(cx),
23622                    lib_text,
23623                    "Other file should be open and active",
23624                );
23625            });
23626        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23627    });
23628
23629    let _other_editor_2 = workspace
23630        .update_in(cx, |workspace, window, cx| {
23631            workspace.open_path(
23632                (worktree_id, rel_path("lib.rs")),
23633                Some(pane_2.downgrade()),
23634                true,
23635                window,
23636                cx,
23637            )
23638        })
23639        .unwrap()
23640        .await
23641        .downcast::<Editor>()
23642        .unwrap();
23643    pane_2
23644        .update_in(cx, |pane, window, cx| {
23645            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23646        })
23647        .await
23648        .unwrap();
23649    drop(editor_2);
23650    pane_2.update(cx, |pane, cx| {
23651        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23652        open_editor.update(cx, |editor, cx| {
23653            assert_eq!(
23654                editor.display_text(cx),
23655                lib_text,
23656                "Other file should be open and active in another panel too",
23657            );
23658        });
23659        assert_eq!(
23660            pane.items().count(),
23661            1,
23662            "No other editors should be open in another pane",
23663        );
23664    });
23665
23666    let _editor_1_reopened = workspace
23667        .update_in(cx, |workspace, window, cx| {
23668            workspace.open_path(
23669                (worktree_id, rel_path("main.rs")),
23670                Some(pane_1.downgrade()),
23671                true,
23672                window,
23673                cx,
23674            )
23675        })
23676        .unwrap()
23677        .await
23678        .downcast::<Editor>()
23679        .unwrap();
23680    let _editor_2_reopened = workspace
23681        .update_in(cx, |workspace, window, cx| {
23682            workspace.open_path(
23683                (worktree_id, rel_path("main.rs")),
23684                Some(pane_2.downgrade()),
23685                true,
23686                window,
23687                cx,
23688            )
23689        })
23690        .unwrap()
23691        .await
23692        .downcast::<Editor>()
23693        .unwrap();
23694    pane_1.update(cx, |pane, cx| {
23695        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23696        open_editor.update(cx, |editor, cx| {
23697            assert_eq!(
23698                editor.display_text(cx),
23699                main_text,
23700                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23701            );
23702            assert_eq!(
23703                editor
23704                    .selections
23705                    .all::<Point>(cx)
23706                    .into_iter()
23707                    .map(|s| s.range())
23708                    .collect::<Vec<_>>(),
23709                expected_ranges,
23710                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23711            );
23712        })
23713    });
23714    pane_2.update(cx, |pane, cx| {
23715        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23716        open_editor.update(cx, |editor, cx| {
23717            assert_eq!(
23718                editor.display_text(cx),
23719                r#"fn main() {
23720⋯rintln!("1");
23721⋯intln!("2");
23722⋯ntln!("3");
23723println!("4");
23724println!("5");
23725}"#,
23726                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23727            );
23728            assert_eq!(
23729                editor
23730                    .selections
23731                    .all::<Point>(cx)
23732                    .into_iter()
23733                    .map(|s| s.range())
23734                    .collect::<Vec<_>>(),
23735                vec![Point::zero()..Point::zero()],
23736                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23737            );
23738        })
23739    });
23740}
23741
23742#[gpui::test]
23743async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23744    init_test(cx, |_| {});
23745
23746    let fs = FakeFs::new(cx.executor());
23747    let main_text = r#"fn main() {
23748println!("1");
23749println!("2");
23750println!("3");
23751println!("4");
23752println!("5");
23753}"#;
23754    let lib_text = "mod foo {}";
23755    fs.insert_tree(
23756        path!("/a"),
23757        json!({
23758            "lib.rs": lib_text,
23759            "main.rs": main_text,
23760        }),
23761    )
23762    .await;
23763
23764    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23765    let (workspace, cx) =
23766        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23767    let worktree_id = workspace.update(cx, |workspace, cx| {
23768        workspace.project().update(cx, |project, cx| {
23769            project.worktrees(cx).next().unwrap().read(cx).id()
23770        })
23771    });
23772
23773    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23774    let editor = workspace
23775        .update_in(cx, |workspace, window, cx| {
23776            workspace.open_path(
23777                (worktree_id, rel_path("main.rs")),
23778                Some(pane.downgrade()),
23779                true,
23780                window,
23781                cx,
23782            )
23783        })
23784        .unwrap()
23785        .await
23786        .downcast::<Editor>()
23787        .unwrap();
23788    pane.update(cx, |pane, cx| {
23789        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23790        open_editor.update(cx, |editor, cx| {
23791            assert_eq!(
23792                editor.display_text(cx),
23793                main_text,
23794                "Original main.rs text on initial open",
23795            );
23796        })
23797    });
23798    editor.update_in(cx, |editor, window, cx| {
23799        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23800    });
23801
23802    cx.update_global(|store: &mut SettingsStore, cx| {
23803        store.update_user_settings(cx, |s| {
23804            s.workspace.restore_on_file_reopen = Some(false);
23805        });
23806    });
23807    editor.update_in(cx, |editor, window, cx| {
23808        editor.fold_ranges(
23809            vec![
23810                Point::new(1, 0)..Point::new(1, 1),
23811                Point::new(2, 0)..Point::new(2, 2),
23812                Point::new(3, 0)..Point::new(3, 3),
23813            ],
23814            false,
23815            window,
23816            cx,
23817        );
23818    });
23819    pane.update_in(cx, |pane, window, cx| {
23820        pane.close_all_items(&CloseAllItems::default(), window, cx)
23821    })
23822    .await
23823    .unwrap();
23824    pane.update(cx, |pane, _| {
23825        assert!(pane.active_item().is_none());
23826    });
23827    cx.update_global(|store: &mut SettingsStore, cx| {
23828        store.update_user_settings(cx, |s| {
23829            s.workspace.restore_on_file_reopen = Some(true);
23830        });
23831    });
23832
23833    let _editor_reopened = workspace
23834        .update_in(cx, |workspace, window, cx| {
23835            workspace.open_path(
23836                (worktree_id, rel_path("main.rs")),
23837                Some(pane.downgrade()),
23838                true,
23839                window,
23840                cx,
23841            )
23842        })
23843        .unwrap()
23844        .await
23845        .downcast::<Editor>()
23846        .unwrap();
23847    pane.update(cx, |pane, cx| {
23848        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23849        open_editor.update(cx, |editor, cx| {
23850            assert_eq!(
23851                editor.display_text(cx),
23852                main_text,
23853                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23854            );
23855        })
23856    });
23857}
23858
23859#[gpui::test]
23860async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23861    struct EmptyModalView {
23862        focus_handle: gpui::FocusHandle,
23863    }
23864    impl EventEmitter<DismissEvent> for EmptyModalView {}
23865    impl Render for EmptyModalView {
23866        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23867            div()
23868        }
23869    }
23870    impl Focusable for EmptyModalView {
23871        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23872            self.focus_handle.clone()
23873        }
23874    }
23875    impl workspace::ModalView for EmptyModalView {}
23876    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23877        EmptyModalView {
23878            focus_handle: cx.focus_handle(),
23879        }
23880    }
23881
23882    init_test(cx, |_| {});
23883
23884    let fs = FakeFs::new(cx.executor());
23885    let project = Project::test(fs, [], cx).await;
23886    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23887    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23888    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23889    let editor = cx.new_window_entity(|window, cx| {
23890        Editor::new(
23891            EditorMode::full(),
23892            buffer,
23893            Some(project.clone()),
23894            window,
23895            cx,
23896        )
23897    });
23898    workspace
23899        .update(cx, |workspace, window, cx| {
23900            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23901        })
23902        .unwrap();
23903    editor.update_in(cx, |editor, window, cx| {
23904        editor.open_context_menu(&OpenContextMenu, window, cx);
23905        assert!(editor.mouse_context_menu.is_some());
23906    });
23907    workspace
23908        .update(cx, |workspace, window, cx| {
23909            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23910        })
23911        .unwrap();
23912    cx.read(|cx| {
23913        assert!(editor.read(cx).mouse_context_menu.is_none());
23914    });
23915}
23916
23917fn set_linked_edit_ranges(
23918    opening: (Point, Point),
23919    closing: (Point, Point),
23920    editor: &mut Editor,
23921    cx: &mut Context<Editor>,
23922) {
23923    let Some((buffer, _)) = editor
23924        .buffer
23925        .read(cx)
23926        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
23927    else {
23928        panic!("Failed to get buffer for selection position");
23929    };
23930    let buffer = buffer.read(cx);
23931    let buffer_id = buffer.remote_id();
23932    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
23933    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
23934    let mut linked_ranges = HashMap::default();
23935    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
23936    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
23937}
23938
23939#[gpui::test]
23940async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
23941    init_test(cx, |_| {});
23942
23943    let fs = FakeFs::new(cx.executor());
23944    fs.insert_file(path!("/file.html"), Default::default())
23945        .await;
23946
23947    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
23948
23949    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23950    let html_language = Arc::new(Language::new(
23951        LanguageConfig {
23952            name: "HTML".into(),
23953            matcher: LanguageMatcher {
23954                path_suffixes: vec!["html".to_string()],
23955                ..LanguageMatcher::default()
23956            },
23957            brackets: BracketPairConfig {
23958                pairs: vec![BracketPair {
23959                    start: "<".into(),
23960                    end: ">".into(),
23961                    close: true,
23962                    ..Default::default()
23963                }],
23964                ..Default::default()
23965            },
23966            ..Default::default()
23967        },
23968        Some(tree_sitter_html::LANGUAGE.into()),
23969    ));
23970    language_registry.add(html_language);
23971    let mut fake_servers = language_registry.register_fake_lsp(
23972        "HTML",
23973        FakeLspAdapter {
23974            capabilities: lsp::ServerCapabilities {
23975                completion_provider: Some(lsp::CompletionOptions {
23976                    resolve_provider: Some(true),
23977                    ..Default::default()
23978                }),
23979                ..Default::default()
23980            },
23981            ..Default::default()
23982        },
23983    );
23984
23985    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23986    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23987
23988    let worktree_id = workspace
23989        .update(cx, |workspace, _window, cx| {
23990            workspace.project().update(cx, |project, cx| {
23991                project.worktrees(cx).next().unwrap().read(cx).id()
23992            })
23993        })
23994        .unwrap();
23995    project
23996        .update(cx, |project, cx| {
23997            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
23998        })
23999        .await
24000        .unwrap();
24001    let editor = workspace
24002        .update(cx, |workspace, window, cx| {
24003            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24004        })
24005        .unwrap()
24006        .await
24007        .unwrap()
24008        .downcast::<Editor>()
24009        .unwrap();
24010
24011    let fake_server = fake_servers.next().await.unwrap();
24012    editor.update_in(cx, |editor, window, cx| {
24013        editor.set_text("<ad></ad>", window, cx);
24014        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24015            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24016        });
24017        set_linked_edit_ranges(
24018            (Point::new(0, 1), Point::new(0, 3)),
24019            (Point::new(0, 6), Point::new(0, 8)),
24020            editor,
24021            cx,
24022        );
24023    });
24024    let mut completion_handle =
24025        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24026            Ok(Some(lsp::CompletionResponse::Array(vec![
24027                lsp::CompletionItem {
24028                    label: "head".to_string(),
24029                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24030                        lsp::InsertReplaceEdit {
24031                            new_text: "head".to_string(),
24032                            insert: lsp::Range::new(
24033                                lsp::Position::new(0, 1),
24034                                lsp::Position::new(0, 3),
24035                            ),
24036                            replace: lsp::Range::new(
24037                                lsp::Position::new(0, 1),
24038                                lsp::Position::new(0, 3),
24039                            ),
24040                        },
24041                    )),
24042                    ..Default::default()
24043                },
24044            ])))
24045        });
24046    editor.update_in(cx, |editor, window, cx| {
24047        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
24048    });
24049    cx.run_until_parked();
24050    completion_handle.next().await.unwrap();
24051    editor.update(cx, |editor, _| {
24052        assert!(
24053            editor.context_menu_visible(),
24054            "Completion menu should be visible"
24055        );
24056    });
24057    editor.update_in(cx, |editor, window, cx| {
24058        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24059    });
24060    cx.executor().run_until_parked();
24061    editor.update(cx, |editor, cx| {
24062        assert_eq!(editor.text(cx), "<head></head>");
24063    });
24064}
24065
24066#[gpui::test]
24067async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24068    init_test(cx, |_| {});
24069
24070    let mut cx = EditorTestContext::new(cx).await;
24071    let language = Arc::new(Language::new(
24072        LanguageConfig {
24073            name: "TSX".into(),
24074            matcher: LanguageMatcher {
24075                path_suffixes: vec!["tsx".to_string()],
24076                ..LanguageMatcher::default()
24077            },
24078            brackets: BracketPairConfig {
24079                pairs: vec![BracketPair {
24080                    start: "<".into(),
24081                    end: ">".into(),
24082                    close: true,
24083                    ..Default::default()
24084                }],
24085                ..Default::default()
24086            },
24087            linked_edit_characters: HashSet::from_iter(['.']),
24088            ..Default::default()
24089        },
24090        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24091    ));
24092    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24093
24094    // Test typing > does not extend linked pair
24095    cx.set_state("<divˇ<div></div>");
24096    cx.update_editor(|editor, _, cx| {
24097        set_linked_edit_ranges(
24098            (Point::new(0, 1), Point::new(0, 4)),
24099            (Point::new(0, 11), Point::new(0, 14)),
24100            editor,
24101            cx,
24102        );
24103    });
24104    cx.update_editor(|editor, window, cx| {
24105        editor.handle_input(">", window, cx);
24106    });
24107    cx.assert_editor_state("<div>ˇ<div></div>");
24108
24109    // Test typing . do extend linked pair
24110    cx.set_state("<Animatedˇ></Animated>");
24111    cx.update_editor(|editor, _, cx| {
24112        set_linked_edit_ranges(
24113            (Point::new(0, 1), Point::new(0, 9)),
24114            (Point::new(0, 12), Point::new(0, 20)),
24115            editor,
24116            cx,
24117        );
24118    });
24119    cx.update_editor(|editor, window, cx| {
24120        editor.handle_input(".", window, cx);
24121    });
24122    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24123    cx.update_editor(|editor, _, cx| {
24124        set_linked_edit_ranges(
24125            (Point::new(0, 1), Point::new(0, 10)),
24126            (Point::new(0, 13), Point::new(0, 21)),
24127            editor,
24128            cx,
24129        );
24130    });
24131    cx.update_editor(|editor, window, cx| {
24132        editor.handle_input("V", window, cx);
24133    });
24134    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24135}
24136
24137#[gpui::test]
24138async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24139    init_test(cx, |_| {});
24140
24141    let fs = FakeFs::new(cx.executor());
24142    fs.insert_tree(
24143        path!("/root"),
24144        json!({
24145            "a": {
24146                "main.rs": "fn main() {}",
24147            },
24148            "foo": {
24149                "bar": {
24150                    "external_file.rs": "pub mod external {}",
24151                }
24152            }
24153        }),
24154    )
24155    .await;
24156
24157    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24158    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24159    language_registry.add(rust_lang());
24160    let _fake_servers = language_registry.register_fake_lsp(
24161        "Rust",
24162        FakeLspAdapter {
24163            ..FakeLspAdapter::default()
24164        },
24165    );
24166    let (workspace, cx) =
24167        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24168    let worktree_id = workspace.update(cx, |workspace, cx| {
24169        workspace.project().update(cx, |project, cx| {
24170            project.worktrees(cx).next().unwrap().read(cx).id()
24171        })
24172    });
24173
24174    let assert_language_servers_count =
24175        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24176            project.update(cx, |project, cx| {
24177                let current = project
24178                    .lsp_store()
24179                    .read(cx)
24180                    .as_local()
24181                    .unwrap()
24182                    .language_servers
24183                    .len();
24184                assert_eq!(expected, current, "{context}");
24185            });
24186        };
24187
24188    assert_language_servers_count(
24189        0,
24190        "No servers should be running before any file is open",
24191        cx,
24192    );
24193    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24194    let main_editor = workspace
24195        .update_in(cx, |workspace, window, cx| {
24196            workspace.open_path(
24197                (worktree_id, rel_path("main.rs")),
24198                Some(pane.downgrade()),
24199                true,
24200                window,
24201                cx,
24202            )
24203        })
24204        .unwrap()
24205        .await
24206        .downcast::<Editor>()
24207        .unwrap();
24208    pane.update(cx, |pane, cx| {
24209        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24210        open_editor.update(cx, |editor, cx| {
24211            assert_eq!(
24212                editor.display_text(cx),
24213                "fn main() {}",
24214                "Original main.rs text on initial open",
24215            );
24216        });
24217        assert_eq!(open_editor, main_editor);
24218    });
24219    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24220
24221    let external_editor = workspace
24222        .update_in(cx, |workspace, window, cx| {
24223            workspace.open_abs_path(
24224                PathBuf::from("/root/foo/bar/external_file.rs"),
24225                OpenOptions::default(),
24226                window,
24227                cx,
24228            )
24229        })
24230        .await
24231        .expect("opening external file")
24232        .downcast::<Editor>()
24233        .expect("downcasted external file's open element to editor");
24234    pane.update(cx, |pane, cx| {
24235        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24236        open_editor.update(cx, |editor, cx| {
24237            assert_eq!(
24238                editor.display_text(cx),
24239                "pub mod external {}",
24240                "External file is open now",
24241            );
24242        });
24243        assert_eq!(open_editor, external_editor);
24244    });
24245    assert_language_servers_count(
24246        1,
24247        "Second, external, *.rs file should join the existing server",
24248        cx,
24249    );
24250
24251    pane.update_in(cx, |pane, window, cx| {
24252        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24253    })
24254    .await
24255    .unwrap();
24256    pane.update_in(cx, |pane, window, cx| {
24257        pane.navigate_backward(&Default::default(), window, cx);
24258    });
24259    cx.run_until_parked();
24260    pane.update(cx, |pane, cx| {
24261        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24262        open_editor.update(cx, |editor, cx| {
24263            assert_eq!(
24264                editor.display_text(cx),
24265                "pub mod external {}",
24266                "External file is open now",
24267            );
24268        });
24269    });
24270    assert_language_servers_count(
24271        1,
24272        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24273        cx,
24274    );
24275
24276    cx.update(|_, cx| {
24277        workspace::reload(cx);
24278    });
24279    assert_language_servers_count(
24280        1,
24281        "After reloading the worktree with local and external files opened, only one project should be started",
24282        cx,
24283    );
24284}
24285
24286#[gpui::test]
24287async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24288    init_test(cx, |_| {});
24289
24290    let mut cx = EditorTestContext::new(cx).await;
24291    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24292    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24293
24294    // test cursor move to start of each line on tab
24295    // for `if`, `elif`, `else`, `while`, `with` and `for`
24296    cx.set_state(indoc! {"
24297        def main():
24298        ˇ    for item in items:
24299        ˇ        while item.active:
24300        ˇ            if item.value > 10:
24301        ˇ                continue
24302        ˇ            elif item.value < 0:
24303        ˇ                break
24304        ˇ            else:
24305        ˇ                with item.context() as ctx:
24306        ˇ                    yield count
24307        ˇ        else:
24308        ˇ            log('while else')
24309        ˇ    else:
24310        ˇ        log('for else')
24311    "});
24312    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24313    cx.assert_editor_state(indoc! {"
24314        def main():
24315            ˇfor item in items:
24316                ˇwhile item.active:
24317                    ˇif item.value > 10:
24318                        ˇcontinue
24319                    ˇelif item.value < 0:
24320                        ˇbreak
24321                    ˇelse:
24322                        ˇwith item.context() as ctx:
24323                            ˇyield count
24324                ˇelse:
24325                    ˇlog('while else')
24326            ˇelse:
24327                ˇlog('for else')
24328    "});
24329    // test relative indent is preserved when tab
24330    // for `if`, `elif`, `else`, `while`, `with` and `for`
24331    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24332    cx.assert_editor_state(indoc! {"
24333        def main():
24334                ˇfor item in items:
24335                    ˇwhile item.active:
24336                        ˇif item.value > 10:
24337                            ˇcontinue
24338                        ˇelif item.value < 0:
24339                            ˇbreak
24340                        ˇelse:
24341                            ˇwith item.context() as ctx:
24342                                ˇyield count
24343                    ˇelse:
24344                        ˇlog('while else')
24345                ˇelse:
24346                    ˇlog('for else')
24347    "});
24348
24349    // test cursor move to start of each line on tab
24350    // for `try`, `except`, `else`, `finally`, `match` and `def`
24351    cx.set_state(indoc! {"
24352        def main():
24353        ˇ    try:
24354        ˇ        fetch()
24355        ˇ    except ValueError:
24356        ˇ        handle_error()
24357        ˇ    else:
24358        ˇ        match value:
24359        ˇ            case _:
24360        ˇ    finally:
24361        ˇ        def status():
24362        ˇ            return 0
24363    "});
24364    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24365    cx.assert_editor_state(indoc! {"
24366        def main():
24367            ˇtry:
24368                ˇfetch()
24369            ˇexcept ValueError:
24370                ˇhandle_error()
24371            ˇelse:
24372                ˇmatch value:
24373                    ˇcase _:
24374            ˇfinally:
24375                ˇdef status():
24376                    ˇreturn 0
24377    "});
24378    // test relative indent is preserved when tab
24379    // for `try`, `except`, `else`, `finally`, `match` and `def`
24380    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24381    cx.assert_editor_state(indoc! {"
24382        def main():
24383                ˇtry:
24384                    ˇfetch()
24385                ˇexcept ValueError:
24386                    ˇhandle_error()
24387                ˇelse:
24388                    ˇmatch value:
24389                        ˇcase _:
24390                ˇfinally:
24391                    ˇdef status():
24392                        ˇreturn 0
24393    "});
24394}
24395
24396#[gpui::test]
24397async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24398    init_test(cx, |_| {});
24399
24400    let mut cx = EditorTestContext::new(cx).await;
24401    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24402    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24403
24404    // test `else` auto outdents when typed inside `if` block
24405    cx.set_state(indoc! {"
24406        def main():
24407            if i == 2:
24408                return
24409                ˇ
24410    "});
24411    cx.update_editor(|editor, window, cx| {
24412        editor.handle_input("else:", window, cx);
24413    });
24414    cx.assert_editor_state(indoc! {"
24415        def main():
24416            if i == 2:
24417                return
24418            else:ˇ
24419    "});
24420
24421    // test `except` auto outdents when typed inside `try` block
24422    cx.set_state(indoc! {"
24423        def main():
24424            try:
24425                i = 2
24426                ˇ
24427    "});
24428    cx.update_editor(|editor, window, cx| {
24429        editor.handle_input("except:", window, cx);
24430    });
24431    cx.assert_editor_state(indoc! {"
24432        def main():
24433            try:
24434                i = 2
24435            except:ˇ
24436    "});
24437
24438    // test `else` auto outdents when typed inside `except` block
24439    cx.set_state(indoc! {"
24440        def main():
24441            try:
24442                i = 2
24443            except:
24444                j = 2
24445                ˇ
24446    "});
24447    cx.update_editor(|editor, window, cx| {
24448        editor.handle_input("else:", window, cx);
24449    });
24450    cx.assert_editor_state(indoc! {"
24451        def main():
24452            try:
24453                i = 2
24454            except:
24455                j = 2
24456            else:ˇ
24457    "});
24458
24459    // test `finally` auto outdents when typed inside `else` block
24460    cx.set_state(indoc! {"
24461        def main():
24462            try:
24463                i = 2
24464            except:
24465                j = 2
24466            else:
24467                k = 2
24468                ˇ
24469    "});
24470    cx.update_editor(|editor, window, cx| {
24471        editor.handle_input("finally:", window, cx);
24472    });
24473    cx.assert_editor_state(indoc! {"
24474        def main():
24475            try:
24476                i = 2
24477            except:
24478                j = 2
24479            else:
24480                k = 2
24481            finally:ˇ
24482    "});
24483
24484    // test `else` does not outdents when typed inside `except` block right after for block
24485    cx.set_state(indoc! {"
24486        def main():
24487            try:
24488                i = 2
24489            except:
24490                for i in range(n):
24491                    pass
24492                ˇ
24493    "});
24494    cx.update_editor(|editor, window, cx| {
24495        editor.handle_input("else:", window, cx);
24496    });
24497    cx.assert_editor_state(indoc! {"
24498        def main():
24499            try:
24500                i = 2
24501            except:
24502                for i in range(n):
24503                    pass
24504                else:ˇ
24505    "});
24506
24507    // test `finally` auto outdents when typed inside `else` block right after for block
24508    cx.set_state(indoc! {"
24509        def main():
24510            try:
24511                i = 2
24512            except:
24513                j = 2
24514            else:
24515                for i in range(n):
24516                    pass
24517                ˇ
24518    "});
24519    cx.update_editor(|editor, window, cx| {
24520        editor.handle_input("finally:", window, cx);
24521    });
24522    cx.assert_editor_state(indoc! {"
24523        def main():
24524            try:
24525                i = 2
24526            except:
24527                j = 2
24528            else:
24529                for i in range(n):
24530                    pass
24531            finally:ˇ
24532    "});
24533
24534    // test `except` outdents to inner "try" block
24535    cx.set_state(indoc! {"
24536        def main():
24537            try:
24538                i = 2
24539                if i == 2:
24540                    try:
24541                        i = 3
24542                        ˇ
24543    "});
24544    cx.update_editor(|editor, window, cx| {
24545        editor.handle_input("except:", window, cx);
24546    });
24547    cx.assert_editor_state(indoc! {"
24548        def main():
24549            try:
24550                i = 2
24551                if i == 2:
24552                    try:
24553                        i = 3
24554                    except:ˇ
24555    "});
24556
24557    // test `except` outdents to outer "try" block
24558    cx.set_state(indoc! {"
24559        def main():
24560            try:
24561                i = 2
24562                if i == 2:
24563                    try:
24564                        i = 3
24565                ˇ
24566    "});
24567    cx.update_editor(|editor, window, cx| {
24568        editor.handle_input("except:", window, cx);
24569    });
24570    cx.assert_editor_state(indoc! {"
24571        def main():
24572            try:
24573                i = 2
24574                if i == 2:
24575                    try:
24576                        i = 3
24577            except:ˇ
24578    "});
24579
24580    // test `else` stays at correct indent when typed after `for` block
24581    cx.set_state(indoc! {"
24582        def main():
24583            for i in range(10):
24584                if i == 3:
24585                    break
24586            ˇ
24587    "});
24588    cx.update_editor(|editor, window, cx| {
24589        editor.handle_input("else:", window, cx);
24590    });
24591    cx.assert_editor_state(indoc! {"
24592        def main():
24593            for i in range(10):
24594                if i == 3:
24595                    break
24596            else:ˇ
24597    "});
24598
24599    // test does not outdent on typing after line with square brackets
24600    cx.set_state(indoc! {"
24601        def f() -> list[str]:
24602            ˇ
24603    "});
24604    cx.update_editor(|editor, window, cx| {
24605        editor.handle_input("a", window, cx);
24606    });
24607    cx.assert_editor_state(indoc! {"
24608        def f() -> list[str]:
2460924610    "});
24611
24612    // test does not outdent on typing : after case keyword
24613    cx.set_state(indoc! {"
24614        match 1:
24615            caseˇ
24616    "});
24617    cx.update_editor(|editor, window, cx| {
24618        editor.handle_input(":", window, cx);
24619    });
24620    cx.assert_editor_state(indoc! {"
24621        match 1:
24622            case:ˇ
24623    "});
24624}
24625
24626#[gpui::test]
24627async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24628    init_test(cx, |_| {});
24629    update_test_language_settings(cx, |settings| {
24630        settings.defaults.extend_comment_on_newline = Some(false);
24631    });
24632    let mut cx = EditorTestContext::new(cx).await;
24633    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24634    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24635
24636    // test correct indent after newline on comment
24637    cx.set_state(indoc! {"
24638        # COMMENT:ˇ
24639    "});
24640    cx.update_editor(|editor, window, cx| {
24641        editor.newline(&Newline, window, cx);
24642    });
24643    cx.assert_editor_state(indoc! {"
24644        # COMMENT:
24645        ˇ
24646    "});
24647
24648    // test correct indent after newline in brackets
24649    cx.set_state(indoc! {"
24650        {ˇ}
24651    "});
24652    cx.update_editor(|editor, window, cx| {
24653        editor.newline(&Newline, window, cx);
24654    });
24655    cx.run_until_parked();
24656    cx.assert_editor_state(indoc! {"
24657        {
24658            ˇ
24659        }
24660    "});
24661
24662    cx.set_state(indoc! {"
24663        (ˇ)
24664    "});
24665    cx.update_editor(|editor, window, cx| {
24666        editor.newline(&Newline, window, cx);
24667    });
24668    cx.run_until_parked();
24669    cx.assert_editor_state(indoc! {"
24670        (
24671            ˇ
24672        )
24673    "});
24674
24675    // do not indent after empty lists or dictionaries
24676    cx.set_state(indoc! {"
24677        a = []ˇ
24678    "});
24679    cx.update_editor(|editor, window, cx| {
24680        editor.newline(&Newline, window, cx);
24681    });
24682    cx.run_until_parked();
24683    cx.assert_editor_state(indoc! {"
24684        a = []
24685        ˇ
24686    "});
24687}
24688
24689#[gpui::test]
24690async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24691    init_test(cx, |_| {});
24692
24693    let mut cx = EditorTestContext::new(cx).await;
24694    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24695    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24696
24697    // test cursor move to start of each line on tab
24698    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24699    cx.set_state(indoc! {"
24700        function main() {
24701        ˇ    for item in $items; do
24702        ˇ        while [ -n \"$item\" ]; do
24703        ˇ            if [ \"$value\" -gt 10 ]; then
24704        ˇ                continue
24705        ˇ            elif [ \"$value\" -lt 0 ]; then
24706        ˇ                break
24707        ˇ            else
24708        ˇ                echo \"$item\"
24709        ˇ            fi
24710        ˇ        done
24711        ˇ    done
24712        ˇ}
24713    "});
24714    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24715    cx.assert_editor_state(indoc! {"
24716        function main() {
24717            ˇfor item in $items; do
24718                ˇwhile [ -n \"$item\" ]; do
24719                    ˇif [ \"$value\" -gt 10 ]; then
24720                        ˇcontinue
24721                    ˇelif [ \"$value\" -lt 0 ]; then
24722                        ˇbreak
24723                    ˇelse
24724                        ˇecho \"$item\"
24725                    ˇfi
24726                ˇdone
24727            ˇdone
24728        ˇ}
24729    "});
24730    // test relative indent is preserved when tab
24731    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24732    cx.assert_editor_state(indoc! {"
24733        function main() {
24734                ˇfor item in $items; do
24735                    ˇwhile [ -n \"$item\" ]; do
24736                        ˇif [ \"$value\" -gt 10 ]; then
24737                            ˇcontinue
24738                        ˇelif [ \"$value\" -lt 0 ]; then
24739                            ˇbreak
24740                        ˇelse
24741                            ˇecho \"$item\"
24742                        ˇfi
24743                    ˇdone
24744                ˇdone
24745            ˇ}
24746    "});
24747
24748    // test cursor move to start of each line on tab
24749    // for `case` statement with patterns
24750    cx.set_state(indoc! {"
24751        function handle() {
24752        ˇ    case \"$1\" in
24753        ˇ        start)
24754        ˇ            echo \"a\"
24755        ˇ            ;;
24756        ˇ        stop)
24757        ˇ            echo \"b\"
24758        ˇ            ;;
24759        ˇ        *)
24760        ˇ            echo \"c\"
24761        ˇ            ;;
24762        ˇ    esac
24763        ˇ}
24764    "});
24765    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24766    cx.assert_editor_state(indoc! {"
24767        function handle() {
24768            ˇcase \"$1\" in
24769                ˇstart)
24770                    ˇecho \"a\"
24771                    ˇ;;
24772                ˇstop)
24773                    ˇecho \"b\"
24774                    ˇ;;
24775                ˇ*)
24776                    ˇecho \"c\"
24777                    ˇ;;
24778            ˇesac
24779        ˇ}
24780    "});
24781}
24782
24783#[gpui::test]
24784async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24785    init_test(cx, |_| {});
24786
24787    let mut cx = EditorTestContext::new(cx).await;
24788    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24789    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24790
24791    // test indents on comment insert
24792    cx.set_state(indoc! {"
24793        function main() {
24794        ˇ    for item in $items; do
24795        ˇ        while [ -n \"$item\" ]; do
24796        ˇ            if [ \"$value\" -gt 10 ]; then
24797        ˇ                continue
24798        ˇ            elif [ \"$value\" -lt 0 ]; then
24799        ˇ                break
24800        ˇ            else
24801        ˇ                echo \"$item\"
24802        ˇ            fi
24803        ˇ        done
24804        ˇ    done
24805        ˇ}
24806    "});
24807    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24808    cx.assert_editor_state(indoc! {"
24809        function main() {
24810        #ˇ    for item in $items; do
24811        #ˇ        while [ -n \"$item\" ]; do
24812        #ˇ            if [ \"$value\" -gt 10 ]; then
24813        #ˇ                continue
24814        #ˇ            elif [ \"$value\" -lt 0 ]; then
24815        #ˇ                break
24816        #ˇ            else
24817        #ˇ                echo \"$item\"
24818        #ˇ            fi
24819        #ˇ        done
24820        #ˇ    done
24821        #ˇ}
24822    "});
24823}
24824
24825#[gpui::test]
24826async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24827    init_test(cx, |_| {});
24828
24829    let mut cx = EditorTestContext::new(cx).await;
24830    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24831    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24832
24833    // test `else` auto outdents when typed inside `if` block
24834    cx.set_state(indoc! {"
24835        if [ \"$1\" = \"test\" ]; then
24836            echo \"foo bar\"
24837            ˇ
24838    "});
24839    cx.update_editor(|editor, window, cx| {
24840        editor.handle_input("else", window, cx);
24841    });
24842    cx.assert_editor_state(indoc! {"
24843        if [ \"$1\" = \"test\" ]; then
24844            echo \"foo bar\"
24845        elseˇ
24846    "});
24847
24848    // test `elif` auto outdents when typed inside `if` block
24849    cx.set_state(indoc! {"
24850        if [ \"$1\" = \"test\" ]; then
24851            echo \"foo bar\"
24852            ˇ
24853    "});
24854    cx.update_editor(|editor, window, cx| {
24855        editor.handle_input("elif", window, cx);
24856    });
24857    cx.assert_editor_state(indoc! {"
24858        if [ \"$1\" = \"test\" ]; then
24859            echo \"foo bar\"
24860        elifˇ
24861    "});
24862
24863    // test `fi` auto outdents when typed inside `else` block
24864    cx.set_state(indoc! {"
24865        if [ \"$1\" = \"test\" ]; then
24866            echo \"foo bar\"
24867        else
24868            echo \"bar baz\"
24869            ˇ
24870    "});
24871    cx.update_editor(|editor, window, cx| {
24872        editor.handle_input("fi", window, cx);
24873    });
24874    cx.assert_editor_state(indoc! {"
24875        if [ \"$1\" = \"test\" ]; then
24876            echo \"foo bar\"
24877        else
24878            echo \"bar baz\"
24879        fiˇ
24880    "});
24881
24882    // test `done` auto outdents when typed inside `while` block
24883    cx.set_state(indoc! {"
24884        while read line; do
24885            echo \"$line\"
24886            ˇ
24887    "});
24888    cx.update_editor(|editor, window, cx| {
24889        editor.handle_input("done", window, cx);
24890    });
24891    cx.assert_editor_state(indoc! {"
24892        while read line; do
24893            echo \"$line\"
24894        doneˇ
24895    "});
24896
24897    // test `done` auto outdents when typed inside `for` block
24898    cx.set_state(indoc! {"
24899        for file in *.txt; do
24900            cat \"$file\"
24901            ˇ
24902    "});
24903    cx.update_editor(|editor, window, cx| {
24904        editor.handle_input("done", window, cx);
24905    });
24906    cx.assert_editor_state(indoc! {"
24907        for file in *.txt; do
24908            cat \"$file\"
24909        doneˇ
24910    "});
24911
24912    // test `esac` auto outdents when typed inside `case` block
24913    cx.set_state(indoc! {"
24914        case \"$1\" in
24915            start)
24916                echo \"foo bar\"
24917                ;;
24918            stop)
24919                echo \"bar baz\"
24920                ;;
24921            ˇ
24922    "});
24923    cx.update_editor(|editor, window, cx| {
24924        editor.handle_input("esac", window, cx);
24925    });
24926    cx.assert_editor_state(indoc! {"
24927        case \"$1\" in
24928            start)
24929                echo \"foo bar\"
24930                ;;
24931            stop)
24932                echo \"bar baz\"
24933                ;;
24934        esacˇ
24935    "});
24936
24937    // test `*)` auto outdents when typed inside `case` block
24938    cx.set_state(indoc! {"
24939        case \"$1\" in
24940            start)
24941                echo \"foo bar\"
24942                ;;
24943                ˇ
24944    "});
24945    cx.update_editor(|editor, window, cx| {
24946        editor.handle_input("*)", window, cx);
24947    });
24948    cx.assert_editor_state(indoc! {"
24949        case \"$1\" in
24950            start)
24951                echo \"foo bar\"
24952                ;;
24953            *)ˇ
24954    "});
24955
24956    // test `fi` outdents to correct level with nested if blocks
24957    cx.set_state(indoc! {"
24958        if [ \"$1\" = \"test\" ]; then
24959            echo \"outer if\"
24960            if [ \"$2\" = \"debug\" ]; then
24961                echo \"inner if\"
24962                ˇ
24963    "});
24964    cx.update_editor(|editor, window, cx| {
24965        editor.handle_input("fi", window, cx);
24966    });
24967    cx.assert_editor_state(indoc! {"
24968        if [ \"$1\" = \"test\" ]; then
24969            echo \"outer if\"
24970            if [ \"$2\" = \"debug\" ]; then
24971                echo \"inner if\"
24972            fiˇ
24973    "});
24974}
24975
24976#[gpui::test]
24977async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
24978    init_test(cx, |_| {});
24979    update_test_language_settings(cx, |settings| {
24980        settings.defaults.extend_comment_on_newline = Some(false);
24981    });
24982    let mut cx = EditorTestContext::new(cx).await;
24983    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24984    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24985
24986    // test correct indent after newline on comment
24987    cx.set_state(indoc! {"
24988        # COMMENT:ˇ
24989    "});
24990    cx.update_editor(|editor, window, cx| {
24991        editor.newline(&Newline, window, cx);
24992    });
24993    cx.assert_editor_state(indoc! {"
24994        # COMMENT:
24995        ˇ
24996    "});
24997
24998    // test correct indent after newline after `then`
24999    cx.set_state(indoc! {"
25000
25001        if [ \"$1\" = \"test\" ]; thenˇ
25002    "});
25003    cx.update_editor(|editor, window, cx| {
25004        editor.newline(&Newline, window, cx);
25005    });
25006    cx.run_until_parked();
25007    cx.assert_editor_state(indoc! {"
25008
25009        if [ \"$1\" = \"test\" ]; then
25010            ˇ
25011    "});
25012
25013    // test correct indent after newline after `else`
25014    cx.set_state(indoc! {"
25015        if [ \"$1\" = \"test\" ]; then
25016        elseˇ
25017    "});
25018    cx.update_editor(|editor, window, cx| {
25019        editor.newline(&Newline, window, cx);
25020    });
25021    cx.run_until_parked();
25022    cx.assert_editor_state(indoc! {"
25023        if [ \"$1\" = \"test\" ]; then
25024        else
25025            ˇ
25026    "});
25027
25028    // test correct indent after newline after `elif`
25029    cx.set_state(indoc! {"
25030        if [ \"$1\" = \"test\" ]; then
25031        elifˇ
25032    "});
25033    cx.update_editor(|editor, window, cx| {
25034        editor.newline(&Newline, window, cx);
25035    });
25036    cx.run_until_parked();
25037    cx.assert_editor_state(indoc! {"
25038        if [ \"$1\" = \"test\" ]; then
25039        elif
25040            ˇ
25041    "});
25042
25043    // test correct indent after newline after `do`
25044    cx.set_state(indoc! {"
25045        for file in *.txt; doˇ
25046    "});
25047    cx.update_editor(|editor, window, cx| {
25048        editor.newline(&Newline, window, cx);
25049    });
25050    cx.run_until_parked();
25051    cx.assert_editor_state(indoc! {"
25052        for file in *.txt; do
25053            ˇ
25054    "});
25055
25056    // test correct indent after newline after case pattern
25057    cx.set_state(indoc! {"
25058        case \"$1\" in
25059            start)ˇ
25060    "});
25061    cx.update_editor(|editor, window, cx| {
25062        editor.newline(&Newline, window, cx);
25063    });
25064    cx.run_until_parked();
25065    cx.assert_editor_state(indoc! {"
25066        case \"$1\" in
25067            start)
25068                ˇ
25069    "});
25070
25071    // test correct indent after newline after case pattern
25072    cx.set_state(indoc! {"
25073        case \"$1\" in
25074            start)
25075                ;;
25076            *)ˇ
25077    "});
25078    cx.update_editor(|editor, window, cx| {
25079        editor.newline(&Newline, window, cx);
25080    });
25081    cx.run_until_parked();
25082    cx.assert_editor_state(indoc! {"
25083        case \"$1\" in
25084            start)
25085                ;;
25086            *)
25087                ˇ
25088    "});
25089
25090    // test correct indent after newline after function opening brace
25091    cx.set_state(indoc! {"
25092        function test() {ˇ}
25093    "});
25094    cx.update_editor(|editor, window, cx| {
25095        editor.newline(&Newline, window, cx);
25096    });
25097    cx.run_until_parked();
25098    cx.assert_editor_state(indoc! {"
25099        function test() {
25100            ˇ
25101        }
25102    "});
25103
25104    // test no extra indent after semicolon on same line
25105    cx.set_state(indoc! {"
25106        echo \"test\"25107    "});
25108    cx.update_editor(|editor, window, cx| {
25109        editor.newline(&Newline, window, cx);
25110    });
25111    cx.run_until_parked();
25112    cx.assert_editor_state(indoc! {"
25113        echo \"test\";
25114        ˇ
25115    "});
25116}
25117
25118fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25119    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25120    point..point
25121}
25122
25123#[track_caller]
25124fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25125    let (text, ranges) = marked_text_ranges(marked_text, true);
25126    assert_eq!(editor.text(cx), text);
25127    assert_eq!(
25128        editor.selections.ranges(cx),
25129        ranges,
25130        "Assert selections are {}",
25131        marked_text
25132    );
25133}
25134
25135pub fn handle_signature_help_request(
25136    cx: &mut EditorLspTestContext,
25137    mocked_response: lsp::SignatureHelp,
25138) -> impl Future<Output = ()> + use<> {
25139    let mut request =
25140        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25141            let mocked_response = mocked_response.clone();
25142            async move { Ok(Some(mocked_response)) }
25143        });
25144
25145    async move {
25146        request.next().await;
25147    }
25148}
25149
25150#[track_caller]
25151pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25152    cx.update_editor(|editor, _, _| {
25153        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25154            let entries = menu.entries.borrow();
25155            let entries = entries
25156                .iter()
25157                .map(|entry| entry.string.as_str())
25158                .collect::<Vec<_>>();
25159            assert_eq!(entries, expected);
25160        } else {
25161            panic!("Expected completions menu");
25162        }
25163    });
25164}
25165
25166/// Handle completion request passing a marked string specifying where the completion
25167/// should be triggered from using '|' character, what range should be replaced, and what completions
25168/// should be returned using '<' and '>' to delimit the range.
25169///
25170/// Also see `handle_completion_request_with_insert_and_replace`.
25171#[track_caller]
25172pub fn handle_completion_request(
25173    marked_string: &str,
25174    completions: Vec<&'static str>,
25175    is_incomplete: bool,
25176    counter: Arc<AtomicUsize>,
25177    cx: &mut EditorLspTestContext,
25178) -> impl Future<Output = ()> {
25179    let complete_from_marker: TextRangeMarker = '|'.into();
25180    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25181    let (_, mut marked_ranges) = marked_text_ranges_by(
25182        marked_string,
25183        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25184    );
25185
25186    let complete_from_position =
25187        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25188    let replace_range =
25189        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25190
25191    let mut request =
25192        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25193            let completions = completions.clone();
25194            counter.fetch_add(1, atomic::Ordering::Release);
25195            async move {
25196                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25197                assert_eq!(
25198                    params.text_document_position.position,
25199                    complete_from_position
25200                );
25201                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25202                    is_incomplete,
25203                    item_defaults: None,
25204                    items: completions
25205                        .iter()
25206                        .map(|completion_text| lsp::CompletionItem {
25207                            label: completion_text.to_string(),
25208                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25209                                range: replace_range,
25210                                new_text: completion_text.to_string(),
25211                            })),
25212                            ..Default::default()
25213                        })
25214                        .collect(),
25215                })))
25216            }
25217        });
25218
25219    async move {
25220        request.next().await;
25221    }
25222}
25223
25224/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25225/// given instead, which also contains an `insert` range.
25226///
25227/// This function uses markers to define ranges:
25228/// - `|` marks the cursor position
25229/// - `<>` marks the replace range
25230/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25231pub fn handle_completion_request_with_insert_and_replace(
25232    cx: &mut EditorLspTestContext,
25233    marked_string: &str,
25234    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25235    counter: Arc<AtomicUsize>,
25236) -> impl Future<Output = ()> {
25237    let complete_from_marker: TextRangeMarker = '|'.into();
25238    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25239    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25240
25241    let (_, mut marked_ranges) = marked_text_ranges_by(
25242        marked_string,
25243        vec![
25244            complete_from_marker.clone(),
25245            replace_range_marker.clone(),
25246            insert_range_marker.clone(),
25247        ],
25248    );
25249
25250    let complete_from_position =
25251        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25252    let replace_range =
25253        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25254
25255    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25256        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25257        _ => lsp::Range {
25258            start: replace_range.start,
25259            end: complete_from_position,
25260        },
25261    };
25262
25263    let mut request =
25264        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25265            let completions = completions.clone();
25266            counter.fetch_add(1, atomic::Ordering::Release);
25267            async move {
25268                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25269                assert_eq!(
25270                    params.text_document_position.position, complete_from_position,
25271                    "marker `|` position doesn't match",
25272                );
25273                Ok(Some(lsp::CompletionResponse::Array(
25274                    completions
25275                        .iter()
25276                        .map(|(label, new_text)| lsp::CompletionItem {
25277                            label: label.to_string(),
25278                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25279                                lsp::InsertReplaceEdit {
25280                                    insert: insert_range,
25281                                    replace: replace_range,
25282                                    new_text: new_text.to_string(),
25283                                },
25284                            )),
25285                            ..Default::default()
25286                        })
25287                        .collect(),
25288                )))
25289            }
25290        });
25291
25292    async move {
25293        request.next().await;
25294    }
25295}
25296
25297fn handle_resolve_completion_request(
25298    cx: &mut EditorLspTestContext,
25299    edits: Option<Vec<(&'static str, &'static str)>>,
25300) -> impl Future<Output = ()> {
25301    let edits = edits.map(|edits| {
25302        edits
25303            .iter()
25304            .map(|(marked_string, new_text)| {
25305                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25306                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25307                lsp::TextEdit::new(replace_range, new_text.to_string())
25308            })
25309            .collect::<Vec<_>>()
25310    });
25311
25312    let mut request =
25313        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25314            let edits = edits.clone();
25315            async move {
25316                Ok(lsp::CompletionItem {
25317                    additional_text_edits: edits,
25318                    ..Default::default()
25319                })
25320            }
25321        });
25322
25323    async move {
25324        request.next().await;
25325    }
25326}
25327
25328pub(crate) fn update_test_language_settings(
25329    cx: &mut TestAppContext,
25330    f: impl Fn(&mut AllLanguageSettingsContent),
25331) {
25332    cx.update(|cx| {
25333        SettingsStore::update_global(cx, |store, cx| {
25334            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25335        });
25336    });
25337}
25338
25339pub(crate) fn update_test_project_settings(
25340    cx: &mut TestAppContext,
25341    f: impl Fn(&mut ProjectSettingsContent),
25342) {
25343    cx.update(|cx| {
25344        SettingsStore::update_global(cx, |store, cx| {
25345            store.update_user_settings(cx, |settings| f(&mut settings.project));
25346        });
25347    });
25348}
25349
25350pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25351    cx.update(|cx| {
25352        assets::Assets.load_test_fonts(cx);
25353        let store = SettingsStore::test(cx);
25354        cx.set_global(store);
25355        theme::init(theme::LoadThemes::JustBase, cx);
25356        release_channel::init(SemanticVersion::default(), cx);
25357        client::init_settings(cx);
25358        language::init(cx);
25359        Project::init_settings(cx);
25360        workspace::init_settings(cx);
25361        crate::init(cx);
25362    });
25363    zlog::init_test();
25364    update_test_language_settings(cx, f);
25365}
25366
25367#[track_caller]
25368fn assert_hunk_revert(
25369    not_reverted_text_with_selections: &str,
25370    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25371    expected_reverted_text_with_selections: &str,
25372    base_text: &str,
25373    cx: &mut EditorLspTestContext,
25374) {
25375    cx.set_state(not_reverted_text_with_selections);
25376    cx.set_head_text(base_text);
25377    cx.executor().run_until_parked();
25378
25379    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25380        let snapshot = editor.snapshot(window, cx);
25381        let reverted_hunk_statuses = snapshot
25382            .buffer_snapshot()
25383            .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
25384            .map(|hunk| hunk.status().kind)
25385            .collect::<Vec<_>>();
25386
25387        editor.git_restore(&Default::default(), window, cx);
25388        reverted_hunk_statuses
25389    });
25390    cx.executor().run_until_parked();
25391    cx.assert_editor_state(expected_reverted_text_with_selections);
25392    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25393}
25394
25395#[gpui::test(iterations = 10)]
25396async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25397    init_test(cx, |_| {});
25398
25399    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25400    let counter = diagnostic_requests.clone();
25401
25402    let fs = FakeFs::new(cx.executor());
25403    fs.insert_tree(
25404        path!("/a"),
25405        json!({
25406            "first.rs": "fn main() { let a = 5; }",
25407            "second.rs": "// Test file",
25408        }),
25409    )
25410    .await;
25411
25412    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25413    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25414    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25415
25416    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25417    language_registry.add(rust_lang());
25418    let mut fake_servers = language_registry.register_fake_lsp(
25419        "Rust",
25420        FakeLspAdapter {
25421            capabilities: lsp::ServerCapabilities {
25422                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25423                    lsp::DiagnosticOptions {
25424                        identifier: None,
25425                        inter_file_dependencies: true,
25426                        workspace_diagnostics: true,
25427                        work_done_progress_options: Default::default(),
25428                    },
25429                )),
25430                ..Default::default()
25431            },
25432            ..Default::default()
25433        },
25434    );
25435
25436    let editor = workspace
25437        .update(cx, |workspace, window, cx| {
25438            workspace.open_abs_path(
25439                PathBuf::from(path!("/a/first.rs")),
25440                OpenOptions::default(),
25441                window,
25442                cx,
25443            )
25444        })
25445        .unwrap()
25446        .await
25447        .unwrap()
25448        .downcast::<Editor>()
25449        .unwrap();
25450    let fake_server = fake_servers.next().await.unwrap();
25451    let server_id = fake_server.server.server_id();
25452    let mut first_request = fake_server
25453        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25454            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25455            let result_id = Some(new_result_id.to_string());
25456            assert_eq!(
25457                params.text_document.uri,
25458                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25459            );
25460            async move {
25461                Ok(lsp::DocumentDiagnosticReportResult::Report(
25462                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25463                        related_documents: None,
25464                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25465                            items: Vec::new(),
25466                            result_id,
25467                        },
25468                    }),
25469                ))
25470            }
25471        });
25472
25473    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25474        project.update(cx, |project, cx| {
25475            let buffer_id = editor
25476                .read(cx)
25477                .buffer()
25478                .read(cx)
25479                .as_singleton()
25480                .expect("created a singleton buffer")
25481                .read(cx)
25482                .remote_id();
25483            let buffer_result_id = project
25484                .lsp_store()
25485                .read(cx)
25486                .result_id(server_id, buffer_id, cx);
25487            assert_eq!(expected, buffer_result_id);
25488        });
25489    };
25490
25491    ensure_result_id(None, cx);
25492    cx.executor().advance_clock(Duration::from_millis(60));
25493    cx.executor().run_until_parked();
25494    assert_eq!(
25495        diagnostic_requests.load(atomic::Ordering::Acquire),
25496        1,
25497        "Opening file should trigger diagnostic request"
25498    );
25499    first_request
25500        .next()
25501        .await
25502        .expect("should have sent the first diagnostics pull request");
25503    ensure_result_id(Some("1".to_string()), cx);
25504
25505    // Editing should trigger diagnostics
25506    editor.update_in(cx, |editor, window, cx| {
25507        editor.handle_input("2", window, cx)
25508    });
25509    cx.executor().advance_clock(Duration::from_millis(60));
25510    cx.executor().run_until_parked();
25511    assert_eq!(
25512        diagnostic_requests.load(atomic::Ordering::Acquire),
25513        2,
25514        "Editing should trigger diagnostic request"
25515    );
25516    ensure_result_id(Some("2".to_string()), cx);
25517
25518    // Moving cursor should not trigger diagnostic request
25519    editor.update_in(cx, |editor, window, cx| {
25520        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25521            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25522        });
25523    });
25524    cx.executor().advance_clock(Duration::from_millis(60));
25525    cx.executor().run_until_parked();
25526    assert_eq!(
25527        diagnostic_requests.load(atomic::Ordering::Acquire),
25528        2,
25529        "Cursor movement should not trigger diagnostic request"
25530    );
25531    ensure_result_id(Some("2".to_string()), cx);
25532    // Multiple rapid edits should be debounced
25533    for _ in 0..5 {
25534        editor.update_in(cx, |editor, window, cx| {
25535            editor.handle_input("x", window, cx)
25536        });
25537    }
25538    cx.executor().advance_clock(Duration::from_millis(60));
25539    cx.executor().run_until_parked();
25540
25541    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25542    assert!(
25543        final_requests <= 4,
25544        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25545    );
25546    ensure_result_id(Some(final_requests.to_string()), cx);
25547}
25548
25549#[gpui::test]
25550async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25551    // Regression test for issue #11671
25552    // Previously, adding a cursor after moving multiple cursors would reset
25553    // the cursor count instead of adding to the existing cursors.
25554    init_test(cx, |_| {});
25555    let mut cx = EditorTestContext::new(cx).await;
25556
25557    // Create a simple buffer with cursor at start
25558    cx.set_state(indoc! {"
25559        ˇaaaa
25560        bbbb
25561        cccc
25562        dddd
25563        eeee
25564        ffff
25565        gggg
25566        hhhh"});
25567
25568    // Add 2 cursors below (so we have 3 total)
25569    cx.update_editor(|editor, window, cx| {
25570        editor.add_selection_below(&Default::default(), window, cx);
25571        editor.add_selection_below(&Default::default(), window, cx);
25572    });
25573
25574    // Verify we have 3 cursors
25575    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25576    assert_eq!(
25577        initial_count, 3,
25578        "Should have 3 cursors after adding 2 below"
25579    );
25580
25581    // Move down one line
25582    cx.update_editor(|editor, window, cx| {
25583        editor.move_down(&MoveDown, window, cx);
25584    });
25585
25586    // Add another cursor below
25587    cx.update_editor(|editor, window, cx| {
25588        editor.add_selection_below(&Default::default(), window, cx);
25589    });
25590
25591    // Should now have 4 cursors (3 original + 1 new)
25592    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25593    assert_eq!(
25594        final_count, 4,
25595        "Should have 4 cursors after moving and adding another"
25596    );
25597}
25598
25599#[gpui::test(iterations = 10)]
25600async fn test_document_colors(cx: &mut TestAppContext) {
25601    let expected_color = Rgba {
25602        r: 0.33,
25603        g: 0.33,
25604        b: 0.33,
25605        a: 0.33,
25606    };
25607
25608    init_test(cx, |_| {});
25609
25610    let fs = FakeFs::new(cx.executor());
25611    fs.insert_tree(
25612        path!("/a"),
25613        json!({
25614            "first.rs": "fn main() { let a = 5; }",
25615        }),
25616    )
25617    .await;
25618
25619    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25620    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25621    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25622
25623    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25624    language_registry.add(rust_lang());
25625    let mut fake_servers = language_registry.register_fake_lsp(
25626        "Rust",
25627        FakeLspAdapter {
25628            capabilities: lsp::ServerCapabilities {
25629                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25630                ..lsp::ServerCapabilities::default()
25631            },
25632            name: "rust-analyzer",
25633            ..FakeLspAdapter::default()
25634        },
25635    );
25636    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25637        "Rust",
25638        FakeLspAdapter {
25639            capabilities: lsp::ServerCapabilities {
25640                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25641                ..lsp::ServerCapabilities::default()
25642            },
25643            name: "not-rust-analyzer",
25644            ..FakeLspAdapter::default()
25645        },
25646    );
25647
25648    let editor = workspace
25649        .update(cx, |workspace, window, cx| {
25650            workspace.open_abs_path(
25651                PathBuf::from(path!("/a/first.rs")),
25652                OpenOptions::default(),
25653                window,
25654                cx,
25655            )
25656        })
25657        .unwrap()
25658        .await
25659        .unwrap()
25660        .downcast::<Editor>()
25661        .unwrap();
25662    let fake_language_server = fake_servers.next().await.unwrap();
25663    let fake_language_server_without_capabilities =
25664        fake_servers_without_capabilities.next().await.unwrap();
25665    let requests_made = Arc::new(AtomicUsize::new(0));
25666    let closure_requests_made = Arc::clone(&requests_made);
25667    let mut color_request_handle = fake_language_server
25668        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25669            let requests_made = Arc::clone(&closure_requests_made);
25670            async move {
25671                assert_eq!(
25672                    params.text_document.uri,
25673                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25674                );
25675                requests_made.fetch_add(1, atomic::Ordering::Release);
25676                Ok(vec![
25677                    lsp::ColorInformation {
25678                        range: lsp::Range {
25679                            start: lsp::Position {
25680                                line: 0,
25681                                character: 0,
25682                            },
25683                            end: lsp::Position {
25684                                line: 0,
25685                                character: 1,
25686                            },
25687                        },
25688                        color: lsp::Color {
25689                            red: 0.33,
25690                            green: 0.33,
25691                            blue: 0.33,
25692                            alpha: 0.33,
25693                        },
25694                    },
25695                    lsp::ColorInformation {
25696                        range: lsp::Range {
25697                            start: lsp::Position {
25698                                line: 0,
25699                                character: 0,
25700                            },
25701                            end: lsp::Position {
25702                                line: 0,
25703                                character: 1,
25704                            },
25705                        },
25706                        color: lsp::Color {
25707                            red: 0.33,
25708                            green: 0.33,
25709                            blue: 0.33,
25710                            alpha: 0.33,
25711                        },
25712                    },
25713                ])
25714            }
25715        });
25716
25717    let _handle = fake_language_server_without_capabilities
25718        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25719            panic!("Should not be called");
25720        });
25721    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
25722    color_request_handle.next().await.unwrap();
25723    cx.run_until_parked();
25724    assert_eq!(
25725        1,
25726        requests_made.load(atomic::Ordering::Acquire),
25727        "Should query for colors once per editor open"
25728    );
25729    editor.update_in(cx, |editor, _, cx| {
25730        assert_eq!(
25731            vec![expected_color],
25732            extract_color_inlays(editor, cx),
25733            "Should have an initial inlay"
25734        );
25735    });
25736
25737    // opening another file in a split should not influence the LSP query counter
25738    workspace
25739        .update(cx, |workspace, window, cx| {
25740            assert_eq!(
25741                workspace.panes().len(),
25742                1,
25743                "Should have one pane with one editor"
25744            );
25745            workspace.move_item_to_pane_in_direction(
25746                &MoveItemToPaneInDirection {
25747                    direction: SplitDirection::Right,
25748                    focus: false,
25749                    clone: true,
25750                },
25751                window,
25752                cx,
25753            );
25754        })
25755        .unwrap();
25756    cx.run_until_parked();
25757    workspace
25758        .update(cx, |workspace, _, cx| {
25759            let panes = workspace.panes();
25760            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25761            for pane in panes {
25762                let editor = pane
25763                    .read(cx)
25764                    .active_item()
25765                    .and_then(|item| item.downcast::<Editor>())
25766                    .expect("Should have opened an editor in each split");
25767                let editor_file = editor
25768                    .read(cx)
25769                    .buffer()
25770                    .read(cx)
25771                    .as_singleton()
25772                    .expect("test deals with singleton buffers")
25773                    .read(cx)
25774                    .file()
25775                    .expect("test buffese should have a file")
25776                    .path();
25777                assert_eq!(
25778                    editor_file.as_ref(),
25779                    rel_path("first.rs"),
25780                    "Both editors should be opened for the same file"
25781                )
25782            }
25783        })
25784        .unwrap();
25785
25786    cx.executor().advance_clock(Duration::from_millis(500));
25787    let save = editor.update_in(cx, |editor, window, cx| {
25788        editor.move_to_end(&MoveToEnd, window, cx);
25789        editor.handle_input("dirty", window, cx);
25790        editor.save(
25791            SaveOptions {
25792                format: true,
25793                autosave: true,
25794            },
25795            project.clone(),
25796            window,
25797            cx,
25798        )
25799    });
25800    save.await.unwrap();
25801
25802    color_request_handle.next().await.unwrap();
25803    cx.run_until_parked();
25804    assert_eq!(
25805        2,
25806        requests_made.load(atomic::Ordering::Acquire),
25807        "Should query for colors once per save (deduplicated) and once per formatting after save"
25808    );
25809
25810    drop(editor);
25811    let close = workspace
25812        .update(cx, |workspace, window, cx| {
25813            workspace.active_pane().update(cx, |pane, cx| {
25814                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25815            })
25816        })
25817        .unwrap();
25818    close.await.unwrap();
25819    let close = workspace
25820        .update(cx, |workspace, window, cx| {
25821            workspace.active_pane().update(cx, |pane, cx| {
25822                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25823            })
25824        })
25825        .unwrap();
25826    close.await.unwrap();
25827    assert_eq!(
25828        2,
25829        requests_made.load(atomic::Ordering::Acquire),
25830        "After saving and closing all editors, no extra requests should be made"
25831    );
25832    workspace
25833        .update(cx, |workspace, _, cx| {
25834            assert!(
25835                workspace.active_item(cx).is_none(),
25836                "Should close all editors"
25837            )
25838        })
25839        .unwrap();
25840
25841    workspace
25842        .update(cx, |workspace, window, cx| {
25843            workspace.active_pane().update(cx, |pane, cx| {
25844                pane.navigate_backward(&workspace::GoBack, window, cx);
25845            })
25846        })
25847        .unwrap();
25848    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
25849    cx.run_until_parked();
25850    let editor = workspace
25851        .update(cx, |workspace, _, cx| {
25852            workspace
25853                .active_item(cx)
25854                .expect("Should have reopened the editor again after navigating back")
25855                .downcast::<Editor>()
25856                .expect("Should be an editor")
25857        })
25858        .unwrap();
25859
25860    assert_eq!(
25861        2,
25862        requests_made.load(atomic::Ordering::Acquire),
25863        "Cache should be reused on buffer close and reopen"
25864    );
25865    editor.update(cx, |editor, cx| {
25866        assert_eq!(
25867            vec![expected_color],
25868            extract_color_inlays(editor, cx),
25869            "Should have an initial inlay"
25870        );
25871    });
25872
25873    drop(color_request_handle);
25874    let closure_requests_made = Arc::clone(&requests_made);
25875    let mut empty_color_request_handle = fake_language_server
25876        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25877            let requests_made = Arc::clone(&closure_requests_made);
25878            async move {
25879                assert_eq!(
25880                    params.text_document.uri,
25881                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25882                );
25883                requests_made.fetch_add(1, atomic::Ordering::Release);
25884                Ok(Vec::new())
25885            }
25886        });
25887    let save = editor.update_in(cx, |editor, window, cx| {
25888        editor.move_to_end(&MoveToEnd, window, cx);
25889        editor.handle_input("dirty_again", window, cx);
25890        editor.save(
25891            SaveOptions {
25892                format: false,
25893                autosave: true,
25894            },
25895            project.clone(),
25896            window,
25897            cx,
25898        )
25899    });
25900    save.await.unwrap();
25901
25902    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
25903    empty_color_request_handle.next().await.unwrap();
25904    cx.run_until_parked();
25905    assert_eq!(
25906        3,
25907        requests_made.load(atomic::Ordering::Acquire),
25908        "Should query for colors once per save only, as formatting was not requested"
25909    );
25910    editor.update(cx, |editor, cx| {
25911        assert_eq!(
25912            Vec::<Rgba>::new(),
25913            extract_color_inlays(editor, cx),
25914            "Should clear all colors when the server returns an empty response"
25915        );
25916    });
25917}
25918
25919#[gpui::test]
25920async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
25921    init_test(cx, |_| {});
25922    let (editor, cx) = cx.add_window_view(Editor::single_line);
25923    editor.update_in(cx, |editor, window, cx| {
25924        editor.set_text("oops\n\nwow\n", window, cx)
25925    });
25926    cx.run_until_parked();
25927    editor.update(cx, |editor, cx| {
25928        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
25929    });
25930    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
25931    cx.run_until_parked();
25932    editor.update(cx, |editor, cx| {
25933        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
25934    });
25935}
25936
25937#[gpui::test]
25938async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
25939    init_test(cx, |_| {});
25940
25941    cx.update(|cx| {
25942        register_project_item::<Editor>(cx);
25943    });
25944
25945    let fs = FakeFs::new(cx.executor());
25946    fs.insert_tree("/root1", json!({})).await;
25947    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
25948        .await;
25949
25950    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
25951    let (workspace, cx) =
25952        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25953
25954    let worktree_id = project.update(cx, |project, cx| {
25955        project.worktrees(cx).next().unwrap().read(cx).id()
25956    });
25957
25958    let handle = workspace
25959        .update_in(cx, |workspace, window, cx| {
25960            let project_path = (worktree_id, rel_path("one.pdf"));
25961            workspace.open_path(project_path, None, true, window, cx)
25962        })
25963        .await
25964        .unwrap();
25965
25966    assert_eq!(
25967        handle.to_any().entity_type(),
25968        TypeId::of::<InvalidBufferView>()
25969    );
25970}
25971
25972#[gpui::test]
25973async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
25974    init_test(cx, |_| {});
25975
25976    let language = Arc::new(Language::new(
25977        LanguageConfig::default(),
25978        Some(tree_sitter_rust::LANGUAGE.into()),
25979    ));
25980
25981    // Test hierarchical sibling navigation
25982    let text = r#"
25983        fn outer() {
25984            if condition {
25985                let a = 1;
25986            }
25987            let b = 2;
25988        }
25989
25990        fn another() {
25991            let c = 3;
25992        }
25993    "#;
25994
25995    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
25996    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25997    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
25998
25999    // Wait for parsing to complete
26000    editor
26001        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
26002        .await;
26003
26004    editor.update_in(cx, |editor, window, cx| {
26005        // Start by selecting "let a = 1;" inside the if block
26006        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26007            s.select_display_ranges([
26008                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
26009            ]);
26010        });
26011
26012        let initial_selection = editor.selections.display_ranges(cx);
26013        assert_eq!(initial_selection.len(), 1, "Should have one selection");
26014
26015        // Test select next sibling - should move up levels to find the next sibling
26016        // Since "let a = 1;" has no siblings in the if block, it should move up
26017        // to find "let b = 2;" which is a sibling of the if block
26018        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26019        let next_selection = editor.selections.display_ranges(cx);
26020
26021        // Should have a selection and it should be different from the initial
26022        assert_eq!(
26023            next_selection.len(),
26024            1,
26025            "Should have one selection after next"
26026        );
26027        assert_ne!(
26028            next_selection[0], initial_selection[0],
26029            "Next sibling selection should be different"
26030        );
26031
26032        // Test hierarchical navigation by going to the end of the current function
26033        // and trying to navigate to the next function
26034        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26035            s.select_display_ranges([
26036                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
26037            ]);
26038        });
26039
26040        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26041        let function_next_selection = editor.selections.display_ranges(cx);
26042
26043        // Should move to the next function
26044        assert_eq!(
26045            function_next_selection.len(),
26046            1,
26047            "Should have one selection after function next"
26048        );
26049
26050        // Test select previous sibling navigation
26051        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
26052        let prev_selection = editor.selections.display_ranges(cx);
26053
26054        // Should have a selection and it should be different
26055        assert_eq!(
26056            prev_selection.len(),
26057            1,
26058            "Should have one selection after prev"
26059        );
26060        assert_ne!(
26061            prev_selection[0], function_next_selection[0],
26062            "Previous sibling selection should be different from next"
26063        );
26064    });
26065}
26066
26067#[gpui::test]
26068async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
26069    init_test(cx, |_| {});
26070
26071    let mut cx = EditorTestContext::new(cx).await;
26072    cx.set_state(
26073        "let ˇvariable = 42;
26074let another = variable + 1;
26075let result = variable * 2;",
26076    );
26077
26078    // Set up document highlights manually (simulating LSP response)
26079    cx.update_editor(|editor, _window, cx| {
26080        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26081
26082        // Create highlights for "variable" occurrences
26083        let highlight_ranges = [
26084            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26085            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26086            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26087        ];
26088
26089        let anchor_ranges: Vec<_> = highlight_ranges
26090            .iter()
26091            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26092            .collect();
26093
26094        editor.highlight_background::<DocumentHighlightRead>(
26095            &anchor_ranges,
26096            |theme| theme.colors().editor_document_highlight_read_background,
26097            cx,
26098        );
26099    });
26100
26101    // Go to next highlight - should move to second "variable"
26102    cx.update_editor(|editor, window, cx| {
26103        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26104    });
26105    cx.assert_editor_state(
26106        "let variable = 42;
26107let another = ˇvariable + 1;
26108let result = variable * 2;",
26109    );
26110
26111    // Go to next highlight - should move to third "variable"
26112    cx.update_editor(|editor, window, cx| {
26113        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26114    });
26115    cx.assert_editor_state(
26116        "let variable = 42;
26117let another = variable + 1;
26118let result = ˇvariable * 2;",
26119    );
26120
26121    // Go to next highlight - should stay at third "variable" (no wrap-around)
26122    cx.update_editor(|editor, window, cx| {
26123        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26124    });
26125    cx.assert_editor_state(
26126        "let variable = 42;
26127let another = variable + 1;
26128let result = ˇvariable * 2;",
26129    );
26130
26131    // Now test going backwards from third position
26132    cx.update_editor(|editor, window, cx| {
26133        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26134    });
26135    cx.assert_editor_state(
26136        "let variable = 42;
26137let another = ˇvariable + 1;
26138let result = variable * 2;",
26139    );
26140
26141    // Go to previous highlight - should move to first "variable"
26142    cx.update_editor(|editor, window, cx| {
26143        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26144    });
26145    cx.assert_editor_state(
26146        "let ˇvariable = 42;
26147let another = variable + 1;
26148let result = variable * 2;",
26149    );
26150
26151    // Go to previous highlight - should stay on first "variable"
26152    cx.update_editor(|editor, window, cx| {
26153        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26154    });
26155    cx.assert_editor_state(
26156        "let ˇvariable = 42;
26157let another = variable + 1;
26158let result = variable * 2;",
26159    );
26160}
26161
26162#[gpui::test]
26163async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26164    cx: &mut gpui::TestAppContext,
26165) {
26166    init_test(cx, |_| {});
26167
26168    let url = "https://zed.dev";
26169
26170    let markdown_language = Arc::new(Language::new(
26171        LanguageConfig {
26172            name: "Markdown".into(),
26173            ..LanguageConfig::default()
26174        },
26175        None,
26176    ));
26177
26178    let mut cx = EditorTestContext::new(cx).await;
26179    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26180    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26181
26182    cx.update_editor(|editor, window, cx| {
26183        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26184        editor.paste(&Paste, window, cx);
26185    });
26186
26187    cx.assert_editor_state(&format!(
26188        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26189    ));
26190}
26191
26192#[gpui::test]
26193async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26194    cx: &mut gpui::TestAppContext,
26195) {
26196    init_test(cx, |_| {});
26197
26198    let url = "https://zed.dev";
26199
26200    let markdown_language = Arc::new(Language::new(
26201        LanguageConfig {
26202            name: "Markdown".into(),
26203            ..LanguageConfig::default()
26204        },
26205        None,
26206    ));
26207
26208    let mut cx = EditorTestContext::new(cx).await;
26209    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26210    cx.set_state(&format!(
26211        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26212    ));
26213
26214    cx.update_editor(|editor, window, cx| {
26215        editor.copy(&Copy, window, cx);
26216    });
26217
26218    cx.set_state(&format!(
26219        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26220    ));
26221
26222    cx.update_editor(|editor, window, cx| {
26223        editor.paste(&Paste, window, cx);
26224    });
26225
26226    cx.assert_editor_state(&format!(
26227        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26228    ));
26229}
26230
26231#[gpui::test]
26232async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26233    cx: &mut gpui::TestAppContext,
26234) {
26235    init_test(cx, |_| {});
26236
26237    let url = "https://zed.dev";
26238
26239    let markdown_language = Arc::new(Language::new(
26240        LanguageConfig {
26241            name: "Markdown".into(),
26242            ..LanguageConfig::default()
26243        },
26244        None,
26245    ));
26246
26247    let mut cx = EditorTestContext::new(cx).await;
26248    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26249    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26250
26251    cx.update_editor(|editor, window, cx| {
26252        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26253        editor.paste(&Paste, window, cx);
26254    });
26255
26256    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26257}
26258
26259#[gpui::test]
26260async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26261    cx: &mut gpui::TestAppContext,
26262) {
26263    init_test(cx, |_| {});
26264
26265    let text = "Awesome";
26266
26267    let markdown_language = Arc::new(Language::new(
26268        LanguageConfig {
26269            name: "Markdown".into(),
26270            ..LanguageConfig::default()
26271        },
26272        None,
26273    ));
26274
26275    let mut cx = EditorTestContext::new(cx).await;
26276    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26277    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26278
26279    cx.update_editor(|editor, window, cx| {
26280        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26281        editor.paste(&Paste, window, cx);
26282    });
26283
26284    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26285}
26286
26287#[gpui::test]
26288async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26289    cx: &mut gpui::TestAppContext,
26290) {
26291    init_test(cx, |_| {});
26292
26293    let url = "https://zed.dev";
26294
26295    let markdown_language = Arc::new(Language::new(
26296        LanguageConfig {
26297            name: "Rust".into(),
26298            ..LanguageConfig::default()
26299        },
26300        None,
26301    ));
26302
26303    let mut cx = EditorTestContext::new(cx).await;
26304    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26305    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26306
26307    cx.update_editor(|editor, window, cx| {
26308        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26309        editor.paste(&Paste, window, cx);
26310    });
26311
26312    cx.assert_editor_state(&format!(
26313        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26314    ));
26315}
26316
26317#[gpui::test]
26318async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26319    cx: &mut TestAppContext,
26320) {
26321    init_test(cx, |_| {});
26322
26323    let url = "https://zed.dev";
26324
26325    let markdown_language = Arc::new(Language::new(
26326        LanguageConfig {
26327            name: "Markdown".into(),
26328            ..LanguageConfig::default()
26329        },
26330        None,
26331    ));
26332
26333    let (editor, cx) = cx.add_window_view(|window, cx| {
26334        let multi_buffer = MultiBuffer::build_multi(
26335            [
26336                ("this will embed -> link", vec![Point::row_range(0..1)]),
26337                ("this will replace -> link", vec![Point::row_range(0..1)]),
26338            ],
26339            cx,
26340        );
26341        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26342        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26343            s.select_ranges(vec![
26344                Point::new(0, 19)..Point::new(0, 23),
26345                Point::new(1, 21)..Point::new(1, 25),
26346            ])
26347        });
26348        let first_buffer_id = multi_buffer
26349            .read(cx)
26350            .excerpt_buffer_ids()
26351            .into_iter()
26352            .next()
26353            .unwrap();
26354        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26355        first_buffer.update(cx, |buffer, cx| {
26356            buffer.set_language(Some(markdown_language.clone()), cx);
26357        });
26358
26359        editor
26360    });
26361    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26362
26363    cx.update_editor(|editor, window, cx| {
26364        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26365        editor.paste(&Paste, window, cx);
26366    });
26367
26368    cx.assert_editor_state(&format!(
26369        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26370    ));
26371}
26372
26373#[gpui::test]
26374async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
26375    init_test(cx, |_| {});
26376
26377    let fs = FakeFs::new(cx.executor());
26378    fs.insert_tree(
26379        path!("/project"),
26380        json!({
26381            "first.rs": "# First Document\nSome content here.",
26382            "second.rs": "Plain text content for second file.",
26383        }),
26384    )
26385    .await;
26386
26387    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
26388    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26389    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26390
26391    let language = rust_lang();
26392    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26393    language_registry.add(language.clone());
26394    let mut fake_servers = language_registry.register_fake_lsp(
26395        "Rust",
26396        FakeLspAdapter {
26397            ..FakeLspAdapter::default()
26398        },
26399    );
26400
26401    let buffer1 = project
26402        .update(cx, |project, cx| {
26403            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
26404        })
26405        .await
26406        .unwrap();
26407    let buffer2 = project
26408        .update(cx, |project, cx| {
26409            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
26410        })
26411        .await
26412        .unwrap();
26413
26414    let multi_buffer = cx.new(|cx| {
26415        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
26416        multi_buffer.set_excerpts_for_path(
26417            PathKey::for_buffer(&buffer1, cx),
26418            buffer1.clone(),
26419            [Point::zero()..buffer1.read(cx).max_point()],
26420            3,
26421            cx,
26422        );
26423        multi_buffer.set_excerpts_for_path(
26424            PathKey::for_buffer(&buffer2, cx),
26425            buffer2.clone(),
26426            [Point::zero()..buffer1.read(cx).max_point()],
26427            3,
26428            cx,
26429        );
26430        multi_buffer
26431    });
26432
26433    let (editor, cx) = cx.add_window_view(|window, cx| {
26434        Editor::new(
26435            EditorMode::full(),
26436            multi_buffer,
26437            Some(project.clone()),
26438            window,
26439            cx,
26440        )
26441    });
26442
26443    let fake_language_server = fake_servers.next().await.unwrap();
26444
26445    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
26446
26447    let save = editor.update_in(cx, |editor, window, cx| {
26448        assert!(editor.is_dirty(cx));
26449
26450        editor.save(
26451            SaveOptions {
26452                format: true,
26453                autosave: true,
26454            },
26455            project,
26456            window,
26457            cx,
26458        )
26459    });
26460    let (start_edit_tx, start_edit_rx) = oneshot::channel();
26461    let (done_edit_tx, done_edit_rx) = oneshot::channel();
26462    let mut done_edit_rx = Some(done_edit_rx);
26463    let mut start_edit_tx = Some(start_edit_tx);
26464
26465    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
26466        start_edit_tx.take().unwrap().send(()).unwrap();
26467        let done_edit_rx = done_edit_rx.take().unwrap();
26468        async move {
26469            done_edit_rx.await.unwrap();
26470            Ok(None)
26471        }
26472    });
26473
26474    start_edit_rx.await.unwrap();
26475    buffer2
26476        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
26477        .unwrap();
26478
26479    done_edit_tx.send(()).unwrap();
26480
26481    save.await.unwrap();
26482    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
26483}
26484
26485#[track_caller]
26486fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26487    editor
26488        .all_inlays(cx)
26489        .into_iter()
26490        .filter_map(|inlay| inlay.get_color())
26491        .map(Rgba::from)
26492        .collect()
26493}